Updraft is an asynchronous Javascript object-relational mapping (ORM)-like library, very similar to
persistence.js. It can work in the browser, on a server using
node.js, or in a Cordova app using the SQLitePlugin
(basically, anywhere WebSQL or a SQLlite interface is supported). It does not support MySQL, in-memory,
local storage, or IndexedDB.
Updraft has not yet been used in any project and is still under heavy development. Expect bugs.
You shouldn't use it if you know nothing about SQL.
Sync is planned but not implemented.
Most databases store only the latest records, which works well when there is only one database. Once you start syncing between multiple databases, you start running into problems. How do you merge records? What if one user deleted a record that another modified?
Updraft addresses these problems by storing baseline records and changes. Users can insert a baseline (that
is, whole) record at any time, but this is intended to be done only once, because only the latest record is
considered the baseline. Subsequent modifications should be made as deltas- you tell the database to made
a change, and it applies the delta to the latest record, as well as keeping a record of all deltas and their
timestamp. Every time a change comes in, it runs all the deltas in order and updates the latest record.
This system makes conflict resolution trivial- if two users change the same record offline, once they resume syncing their changes will automatically merge based on the time they came in.
Deltas are based on React's immutability helpers, which
in turn is based on MongoDB's query language, though
there is no tie to any database. They are immutable operations, meaning they leave the source object untouched.
For example:
var record = {
id: 123,
text: "original text"
};
var delta = {
text: { $set: "new text" }
};
var newRecord = Updraft.update(record, delta);
// record -> { id: 123, text: "original text" }
// newRecord -> { id: 123, text: "new text" }
You should think of Updraft as more of a wrapper over executing SQL statements yourself, rather than a complete ORM
framework. Objects won't be cached or saved unless you do it yourself. You will have to define your primary key
(only one is supported) as well as create and assign a unique value for that key.
You also won't find one-to-one or many-to-many or other SQL-centric ideas. You can define a field of type 'set' where you can have a (homogeneous) set of values or object keys. You will be responsible for tracking object lifetimes and deleting any orphaned entities.
You need a JS environment that supports Promises or you can use a library like lie.
Though written in TypeScript, it will run in any JS environment (browser, node.js)
npm:
npm install --save updraft
Bower:
bower install --save updraft
Basic usage is as follows:
var taskSpec = {
name: 'tasks',
columns: {
id: Updraft.Column.Text().Key(),
description: Updraft.Column.Text(),
done: Updraft.Column.Bool()
}
};
var sqlite3 = require("sqlite3");
var db = new sqlite3.Database("test.db");
var store = new Updraft.createStore({ db: Updraft.createSQLiteWrapper(db) });
var taskTable = store.createTable(taskSpec);
var time = Date.now();
store.open({name: 'my database'})
.then(function() {
var task = {
id: 123,
description: "task description",
done: false
};
// save baseline
return taskTable.add([{ time: time, create: task }]);
})
.then(function() {
var delta = {
description: { $set: "changed description" },
done: { $set: true }
};
// in a real application you would just use Date.now(), since it's probably not the
// same second you created the record
time = time + 1;
// save the change
return taskTable.add([{ time: time, delta: delta }]);
})
.then(function() {
// find the value with id 123. See docs for more advanced query options
return taskTable.find({id: 123});
})
.then(function(results) {
var task = results[0];
// -> { id: 123, description: "changed description" }
})
;
If you use TypeScript, you can use interfaces to make your life easier and let the compiler catch errors:
import D = Updraft.Delta;
import Q = Updraft.Query;
// either set up multiple interfaces for declarations and queries:
interface Task {
id: number;
description: string;
done: boolean;
}
interface TaskDelta {
id: number; // NOTE: database does not support changing the key value
description: D.str;
done: D.bool;
}
interface TaskQuery {
id: Q.num;
description: Q.str;
done: Q.bool;
}
// or use templates to keep things [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)
interface _Task<key, str, bool> {
id: key;
description: str;
done: bool;
}
interface Task extends _Task<number, string, boolean> {}
interface TaskDelta extends _Task<number, D.str, D.bool> {}
interface TaskQuery extends _Task<Q.num, Q.str, Q.bool> {}
// then set up your table
type TaskTable = Updraft.Table<Task, TaskDelta, TaskQuery>;
type TaskTableSpec = Updraft.TableSpec<Task, TaskDelta, TaskQuery>;
const taskSpec: TaskTableSpec = {
name: 'tasks',
columns: {
id: Updraft.Column.Text().Key(),
description: Updraft.Column.Text(),
done: Updraft.Column.Bool()
}
};
// ...
var store = new Updraft.createStore({ db: Updraft.createSQLiteWrapper(db) });
var taskTable: TaskTable = store.createTable(taskSpec);
For advanced usage, see the documentation.
Updraft supports typescript enums and enum-like objects, such as those created using the enum library, with no dependency on any specific library. They will be saved as the object's 'toString()' value and restored using the class's 'get(value)' method. They are stored in the db as strings.
var store = new Updraft.createStore(/* ... */);
var ColorTemperature = new Enum({'Cool', 'Neutral', 'Warm'});
var paintTable = store.createClass({
tableName: 'paints',
columns: {
name: Updraft.Column.Text().Key(),
colorTemp: Updraft.Column.Enum(ColorTemperature)
}
});
var paint = {
name: "cyan",
colorTemp: ColorTemperature.Cool,
};
// ...
paintTable.find({ colorTemp: ColorTemperature.Cool }).then(/* ... */);
For most simple changes, Updraft will have you covered. You can feel free to add a new field, a new class, remove
fields or classes, add or remove indices, and rename fields without needing to do any extra work. You can also change
field types, but because the underlying database is SQLite, the 'type' is only a column
affinity- no schema change/migration will happen; you can always store any
type (int/string/blob/etc) in any field.
During migrations, removed and renamed columns will be preserved not only in the resulting table but also by walking every change and updating the delta objects. Because of this, it might take some time depending on how many records you have.
Not supported:
There is auto-generated documentation in doc/index.html
For examples see the test folder
We'll check out your contribution if you:
We'll do our best to help you out with any contribution issues you may have.
MIT. See LICENSE.txt in this directory.
See clone JS source for API docs
the value that you want to clone, any type allowed
Call clone with circular set to false if you are certain that obj contains no circular references. This will give better performance if needed. There is no error if undefined or null is passed as obj.
to wich the object is to be cloned (optional, defaults to infinity)
the object that you want to clone
Constant for fs.access(). File is visible to the calling process.
Constant for fs.access(). File can be read by the calling process.
Constant for fs.access(). File can be written by the calling process.
Constant for fs.access(). File can be executed by the calling process.
Tests a user's permissions for the file specified by path.
Synchronous version of fs.access. This throws if any accessibility checks fail, and does nothing otherwise.
Asynchronous rename.
No arguments other than a possible exception are given to the completion callback.
Synchronous rename
Destroy any sockets that are currently in use by the agent. It is usually not necessary to do this. However, if you are using an agent with KeepAlive enabled, then it is best to explicitly shut down the agent when you know that it will no longer be used. Otherwise, sockets may hang open for quite a long time before the server terminates them.
Keep sockets around in a pool to be used by other requests in the future. Default = false
When using HTTP KeepAlive, how often to send TCP KeepAlive packets over sockets being kept alive. Default = 1000. Only relevant if keepAlive is set to true.
Maximum number of sockets to leave open in a free state. Only relevant if keepAlive is set to true. Default = 256.
Maximum number of sockets to allow per host. Default for Node 0.10 is 5, default for Node 0.12 is Infinity
Only valid for request obtained from http.Server.
Only valid for response obtained from http.ClientRequest.
Only valid for response obtained from http.ClientRequest.
Only valid for request obtained from http.Server.
Only valid for request obtained from http.Server.
Only valid for response obtained from http.ClientRequest.
Only valid for response obtained from http.ClientRequest.
Only valid for request obtained from http.Server.
Only valid for request obtained from http.Server.
Only valid for response obtained from http.ClientRequest.
Only valid for response obtained from http.ClientRequest.
Only valid for request obtained from http.Server.
A parsed path object generated by path.parse() or consumed by path.format().
The file name including extension (if any) such as 'index.html'
The full directory path such as '/home/user/dir' or 'c:\path\dir'
The file extension (if any) such as '.html'
The file name without extension (if any) such as 'index'
The root of the path such as '/' or 'c:\'
The platform-specific file delimiter. ';' or ':'.
The platform-specific file separator. '\' or '/'.
Return the last portion of a path. Similar to the Unix basename command. Often used to extract the file name from a fully qualified path.
the path to evaluate.
optionally, an extension to remove from the result.
Return the directory name of a path. Similar to the Unix dirname command.
the path to evaluate.
Return the extension of the path, from the last '.' to end of string in the last portion of the path. If there is no '.' in the last portion of the path or the first character of it is '.', then it returns an empty string
the path to evaluate.
Returns a path string from an object - the opposite of parse().
Determines whether {path} is an absolute path. An absolute path will always resolve to the same location, regardless of the working directory.
path to test.
Join all arguments together and normalize the resulting path. Arguments must be strings. In v0.8, non-string arguments were silently ignored. In v0.10 and up, an exception is thrown.
string paths to join.
Join all arguments together and normalize the resulting path. Arguments must be strings. In v0.8, non-string arguments were silently ignored. In v0.10 and up, an exception is thrown.
string paths to join.
Normalize a string path, reducing '..' and '.' parts. When multiple slashes are found, they're replaced by a single one; when the path contains a trailing slash, it is preserved. On Windows backslashes are used.
string path to normalize.
Returns an object from a path string - the opposite of format().
path to evaluate.
Solve the relative path from {from} to {to}. At times we have two absolute paths, and we need to derive the relative path from one to the other. This is actually the reverse transform of path.resolve.
The right-most parameter is considered {to}. Other parameters are considered an array of {from}.
Starting from leftmost {from} paramter, resolves {to} to an absolute path.
If {to} isn't already absolute, {from} arguments are prepended in right to left order, until an absolute path is found. If after using all {from} paths still no absolute path is found, the current working directory is used as well. The resulting path is normalized, and trailing slashes are removed unless the path gets resolved to the root directory.
string paths to join. Non-string arguments are ignored.
Expression to test for truthiness.
Message to display on error.
Expression to test for truthiness.
Message to display on error.
Provides a way to extend the internals of Chai
*
GLOBAL INTERFACES *
*
Allocates a new buffer containing the given {str}.
String to store in buffer.
encoding to use, optional. Default is 'utf8'
Allocates a new buffer of {size} octets.
count of octets to allocate.
Allocates a new buffer containing the given {array} of octets.
The octets to store.
Allocates a new buffer containing the given {array} of octets.
The octets to store.
Gives the actual byte length of a string. encoding defaults to 'utf8'. This is not the same as String.prototype.length since that returns the number of characters in a string.
string to test.
encoding used to evaluate (defaults to 'utf8')
Returns a buffer which is the result of concatenating all the buffers in the list together.
If the list has no items, or if the totalLength is 0, then it returns a zero-length buffer. If the list has exactly one item, then the first item of the list is returned. If the list has more than one item, then a new Buffer is created.
An array of Buffer objects to concatenate
Total length of the buffers when concatenated. If totalLength is not provided, it is read from the buffers in the list. However, this adds an additional loop to the function, so it is faster to provide the length explicitly.
Returns true if {obj} is a Buffer
object to test.
Returns true if {encoding} is a valid encoding argument. Valid string encodings in Node 0.12: 'ascii'|'utf8'|'utf16le'|'ucs2'(alias of 'utf16le')|'base64'|'binary'(deprecated)|'hex'
string to test.
Column in db. Use static methods to create columns.
Set a default value for the column
Create an index for this column for faster queries.
Column is the primary key. Only one column can have this set.
create a column with "BOOL" affinity
a javascript Date objct, stored in db as seconds since Unix epoch (time_t) [note: precision is seconds]
a javascript Date objct, stored in db as seconds since Unix epoch (time_t) [note: precision is seconds]
a typescript enum or javascript object with instance method "toString" and class method "get" (e.g. https://github.com/adrai/enum).
create a column with "INTEGER" affinity
object will be serialized & restored as JSON text
create a column with "REAL" affinity
unordered collection
create a column with "TEXT" affinity
create a column with "TEXT" affinity
An enum class (e.g. (this one)[https://github.com/adrai/enum]) should provide a static method 'get' to resolve strings into enum values
A typescript enum class will have string keys resolving to the enum values
Use verify() to assert state which your program assumes to be true.
Provide sprintf-style format (only %s is supported) and arguments to provide information about what broke and what you were expecting.
String used to indicate the end of the progress bar.
String used to indicate a complete test on the progress bar.
String used to indicate an incomplete test on the progress bar.
String used to indicate the start of the progress bar.
Partial interface for Mocha's Runnable class.
Partial interface for Mocha's Runner class.
Partial interface for Mocha's Suite class.
Partial interface for Mocha's Test class.
Enables growl support.
Runs tests and invokes onComplete() when finished.
Setup mocha with the given options.
Function to allow assertion libraries to throw errors directly into mocha. This is useful when running tests in a browser because window.onerror will only receive the 'message' attribute of the Error.
If you call resolve in the body of the callback passed to the constructor, your promise is fulfilled with result object passed to resolve. If you call reject your promise is rejected with the object passed to resolve. For consistency and debugging (eg stack traces), obj should be an instanceof Error. Any errors thrown in the constructor callback will be implicitly passed to reject().
onFulfilled is called when/if "promise" resolves. onRejected is called when/if "promise" rejects. Both are optional, if either/both are omitted the next onFulfilled/onRejected in the chain is called. Both callbacks have a single parameter , the fulfillment value or rejection reason. "then" returns a new promise equivalent to the value you return from onFulfilled/onRejected after being passed through Promise.resolve. If an error is thrown in the callback, the returned promise rejects with that error.
called when/if "promise" resolves
called when/if "promise" rejects
Make a promise that fulfills when every item in the array fulfills, and rejects if (and when) any item rejects. the array passed to all can be a mixture of promise-like objects and other objects. The fulfillment value is an array (in order) of fulfillment values. The rejection value is the first rejection value.
Make a promise that rejects to obj. For consistency and debugging (eg stack traces), obj should be an instanceof Error
Raw data is stored in instances of the Buffer class. A Buffer is similar to an array of integers but corresponds to a raw memory allocation outside the V8 heap. A Buffer cannot be resized. Valid string encodings: 'ascii'|'utf8'|'utf16le'|'ucs2'(alias of 'utf16le')|'base64'|'binary'(deprecated)|'hex'
*
Node.js v0.12.0 API *
*
*
GLOBAL *
*
Generated using TypeDoc
* MODULES * *