Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' of git://github.com/kriszyp/perstore

Conflicts:

	engines/node/lib/packages.js
  • Loading branch information...
commit 4f2a26ea4a48a1754faa39ceb89c9804cf9f8cce 2 parents 1392275 + 96bcabe
Nathan L Smith authored
3  engines/node/lib/file.js
View
@@ -0,0 +1,3 @@
+var fs = require("fs");
+exports.read = fs.readFileSync;
+exports.write = fs.writeFileSync;
17 engines/node/lib/fs-promise.js
View
@@ -0,0 +1,17 @@
+/**
+* Node fs module that returns promises
+*/
+
+var fs = require("fs"),
+ convertNodeAsyncFunction = require("./promise").convertNodeAsyncFunction;
+
+// convert all the non-sync functions
+for (var i in fs) {
+ if (!(i.match(/Sync$/))) {
+ exports[i] = convertNodeAsyncFunction(fs[i]);
+ }
+}
+
+// convert the functions that don't have a declared callback
+exports.writeFile = convertNodeAsyncFunction(fs.writeFile, true);
+exports.readFile = convertNodeAsyncFunction(fs.readFile, true);
2  engines/node/lib/packages.js
View
@@ -1,3 +1,3 @@
var fs = require("fs");
-exports.root = JSON.parse(fs.cat("local.json").wait());
+exports.root = JSON.parse(fs.readFileSync("local.json"));
554 engines/node/lib/promise.js
View
@@ -0,0 +1,554 @@
+
+// Kris Zyp
+
+// this is based on the CommonJS spec for promises:
+// http://wiki.commonjs.org/wiki/Promises
+// Includes convenience functions for promises, much of this is taken from Tyler Close's ref_send
+// and Kris Kowal's work on promises.
+// // MIT License
+
+// A typical usage:
+// A default Promise constructor can be used to create a self-resolving deferred/promise:
+// var Promise = require("promise").Promise;
+// var promise = new Promise();
+// asyncOperation(function(){
+// Promise.resolve("succesful result");
+// });
+// promise -> given to the consumer
+//
+// A consumer can use the promise
+// promise.then(function(result){
+// ... when the action is complete this is executed ...
+// },
+// function(error){
+// ... executed when the promise fails
+// });
+//
+// Alternately, a provider can create a deferred and resolve it when it completes an action.
+// The deferred object a promise object that provides a separation of consumer and producer to protect
+// promises from being fulfilled by untrusted code.
+// var defer = require("promise").defer;
+// var deferred = defer();
+// asyncOperation(function(){
+// deferred.resolve("succesful result");
+// });
+// deferred.promise -> given to the consumer
+//
+// Another way that a consumer can use the promise (using promise.then is also allowed)
+// var when = require("promise").when;
+// when(promise,function(result){
+// ... when the action is complete this is executed ...
+// },
+// function(error){
+// ... executed when the promise fails
+// });
+try{
+ var enqueue = require("event-queue").enqueue;
+}
+catch(e){
+ // squelch the error, and only complain if the queue is needed
+}
+if(!enqueue){
+ enqueue = (typeof process !== "undefined" && process.nextTick) || function(func){
+ func();
+ }
+}
+var freeze = Object.freeze || function(){};
+
+/**
+ * Default constructor that creates a self-resolving Promise. Not all promise implementations
+ * need to use this constructor.
+ */
+var Promise = function(canceller){
+};
+
+/**
+ * Promise implementations must provide a "then" function.
+ */
+Promise.prototype.then = function(resolvedCallback, errorCallback, progressCallback){
+ throw new TypeError("The Promise base class is abstract, this function must be implemented by the Promise implementation");
+};
+
+/**
+ * If an implementation of a promise supports a concurrency model that allows
+ * execution to block until the promise is resolved, the wait function may be
+ * added.
+ */
+/**
+ * If an implementation of a promise can be cancelled, it may add this function
+ */
+ // Promise.prototype.cancel = function(){
+ // };
+
+Promise.prototype.get = function(propertyName){
+ return this.then(function(value){
+ return value[propertyName];
+ });
+};
+
+Promise.prototype.put = function(propertyName, value){
+ return this.then(function(object){
+ return object[propertyName] = value;
+ });
+};
+
+Promise.prototype.call = function(functionName /*, args */){
+ return this.then(function(value){
+ return value[propertyName].apply(value, Array.prototype.slice.call(arguments, 1));
+ });
+};
+
+/** Dojo/NodeJS methods*/
+Promise.prototype.addCallback = function(callback){
+ return this.then(callback);
+};
+
+Promise.prototype.addErrback = function(errback){
+ return this.then(function(){}, errback);
+};
+
+/*Dojo methods*/
+Promise.prototype.addBoth = function(callback){
+ return this.then(callback, callback);
+};
+
+Promise.prototype.addCallbacks = function(callback, errback){
+ return this.then(callback, errback);
+};
+
+/*NodeJS method*/
+Promise.prototype.wait = function(){
+ return exports.wait(this);
+};
+
+Deferred.prototype = Promise.prototype;
+// A deferred provides an API for creating and resolving a promise.
+exports.Promise = exports.Deferred = exports.defer = defer;
+function defer(){
+ return new Deferred();
+}
+
+var contextHandler = exports.contextHandler = {};
+
+function Deferred(canceller){
+ var result, finished, isError, waiting = [], handled;
+ var promise = this.promise = new Promise();
+ var currentContextHandler = contextHandler.getHandler && contextHandler.getHandler();
+
+ function notifyAll(value){
+ if(finished){
+ throw new Error("This deferred has already been resolved");
+ }
+ result = value;
+ finished = true;
+ for(var i = 0; i < waiting.length; i++){
+ notify(waiting[i]);
+ }
+ }
+ function notify(listener){
+ var func = (isError ? listener.error : listener.resolved);
+ if(func){
+ handled = true;
+ enqueue(function(){
+ if(currentContextHandler){
+ currentContextHandler.resume();
+ }
+ try{
+ var newResult = func(result);
+ if(newResult && typeof newResult.then === "function"){
+ newResult.then(listener.deferred.resolve, listener.deferred.reject);
+ return;
+ }
+ listener.deferred.resolve(newResult);
+ }
+ catch(e){
+ listener.deferred.reject(e);
+ }
+ finally{
+ if(currentContextHandler){
+ currentContextHandler.suspend();
+ }
+ }
+ });
+ }
+ else{
+ if(isError){
+ if (listener.deferred.reject(result, true)) {
+ handled = true;
+ }
+ }
+ else{
+ listener.deferred.resolve.apply(listener.deferred, result);
+ }
+ }
+ }
+ // calling resolve will resolve the promise
+ this.resolve = this.callback = this.emitSuccess = function(value){
+ notifyAll(value);
+ };
+
+ // calling error will indicate that the promise failed
+ var reject = this.reject = this.errback = this.emitError = function(error, dontThrow){
+ isError = true;
+ notifyAll(error);
+ if (!dontThrow) {
+ enqueue(function () {
+ if (!handled) {
+ throw error;
+ }
+ });
+ }
+ return handled;
+ };
+
+ // call progress to provide updates on the progress on the completion of the promise
+ this.progress = function(update){
+ for(var i = 0; i < waiting.length; i++){
+ var progress = waiting[i].progress;
+ progress && progress(update);
+ }
+ }
+ // provide the implementation of the promise
+ this.then = promise.then = function(resolvedCallback, errorCallback, progressCallback){
+ var returnDeferred = new Deferred(promise.cancel);
+ var listener = {resolved: resolvedCallback, error: errorCallback, progress: progressCallback, deferred: returnDeferred};
+ if(finished){
+ notify(listener);
+ }
+ else{
+ waiting.push(listener);
+ }
+ return returnDeferred.promise;
+ };
+ var timeout;
+ if(typeof setTimeout !== "undefined") {
+ this.timeout = function (ms) {
+ if (ms === undefined) {
+ return timeout;
+ }
+ timeout = ms;
+ setTimeout(function () {
+ if (!finished) {
+ if (promise.cancel) {
+ promise.cancel(new Error("timeout"));
+ }
+ else {
+ reject(new Error("timeout"));
+ }
+ }
+ }, ms);
+ return promise;
+ };
+ }
+
+ if(canceller){
+ this.cancel = promise.cancel = function(){
+ var error = canceller();
+ if(!(error instanceof Error)){
+ error = new Error(error);
+ }
+ reject(error);
+ }
+ }
+ freeze(promise);
+};
+
+function perform(value, async, sync){
+ try{
+ if(value && typeof value.then === "function"){
+ value = async(value);
+ }
+ else{
+ value = sync(value);
+ }
+ if(value && typeof value.then === "function"){
+ return value;
+ }
+ var deferred = new Deferred();
+ deferred.resolve(value);
+ return deferred.promise;
+ }catch(e){
+ var deferred = new Deferred();
+ deferred.reject(e);
+ return deferred.promise;
+ }
+
+}
+/**
+ * Promise manager to make it easier to consume promises
+ */
+
+/**
+ * Registers an observer on a promise.
+ * @param value promise or value to observe
+ * @param resolvedCallback function to be called with the resolved value
+ * @param rejectCallback function to be called with the rejection reason
+ * @param progressCallback function to be called when progress is made
+ * @return promise for the return value from the invoked callback
+ */
+exports.whenPromise = function(value, resolvedCallback, rejectCallback, progressCallback){
+ return perform(value, function(value){
+ return value.then(resolvedCallback, rejectCallback, progressCallback);
+ },
+ function(value){
+ return resolvedCallback(value);
+ });
+};
+/**
+ * Registers an observer on a promise.
+ * @param value promise or value to observe
+ * @param resolvedCallback function to be called with the resolved value
+ * @param rejectCallback function to be called with the rejection reason
+ * @param progressCallback function to be called when progress is made
+ * @return promise for the return value from the invoked callback or the value if it
+ * is a non-promise value
+ */
+exports.when = function(value, resolvedCallback, rejectCallback, progressCallback){
+ if(value && typeof value.then === "function"){
+ return exports.whenPromise(value, resolvedCallback, rejectCallback, progressCallback);
+ }
+ return resolvedCallback(value);
+};
+
+/**
+ * Gets the value of a property in a future turn.
+ * @param target promise or value for target object
+ * @param property name of property to get
+ * @return promise for the property value
+ */
+exports.get = function(target, property){
+ return perform(target, function(target){
+ return target.get(property);
+ },
+ function(target){
+ return target[property]
+ });
+};
+
+/**
+ * Invokes a method in a future turn.
+ * @param target promise or value for target object
+ * @param methodName name of method to invoke
+ * @param args array of invocation arguments
+ * @return promise for the return value
+ */
+exports.post = function(target, methodName, args){
+ return perform(target, function(target){
+ return target.call(property, args);
+ },
+ function(target){
+ return target[methodName].apply(target, args);
+ });
+};
+
+/**
+ * Sets the value of a property in a future turn.
+ * @param target promise or value for target object
+ * @param property name of property to set
+ * @param value new value of property
+ * @return promise for the return value
+ */
+exports.put = function(target, property, value){
+ return perform(target, function(target){
+ return target.put(property, value);
+ },
+ function(target){
+ return target[property] = value;
+ });
+};
+
+
+/**
+ * Waits for the given promise to finish, blocking (and executing other events)
+ * if necessary to wait for the promise to finish. If target is not a promise
+ * it will return the target immediately. If the promise results in an reject,
+ * that reject will be thrown.
+ * @param target promise or value to wait for.
+ * @return the value of the promise;
+ */
+exports.wait = function(target){
+ if(!queue){
+ throw new Error("Can not wait, the event-queue module is not available");
+ }
+ if(target && typeof target.then === "function"){
+ var isFinished, isError, result;
+ target.then(function(value){
+ isFinished = true;
+ result = value;
+ },
+ function(error){
+ isFinished = true;
+ isError = true;
+ result = error;
+ });
+ while(!isFinished){
+ queue.processNextEvent(true);
+ }
+ if(isError){
+ throw result;
+ }
+ return result;
+ }
+ else{
+ return target;
+ }
+};
+
+
+
+/**
+ * Takes an array of promises and returns a promise that is fulfilled once all
+ * the promises in the array are fulfilled
+ * @param array The array of promises
+ * @return the promise that is fulfilled when all the array is fulfilled, resolved to the array of results
+ */
+exports.all = function(array){
+ var deferred = new Deferred();
+ if(!(array instanceof Array)){
+ array = Array.prototype.slice.call(arguments);
+ }
+ var fulfilled = 0, length = array.length;
+ var results = [];
+ array.forEach(function(promise, index){
+ exports.when(promise, each, each);
+ function each(value){
+ results[index] = value;
+ fulfilled++;
+ if(fulfilled === length){
+ deferred.resolve(results);
+ }
+ }
+ });
+ return deferred.promise;
+};
+
+/**
+ * Takes an array of promises and returns a promise that is fulfilled when the first
+ * promise in the array of promises is fulfilled
+ * @param array The array of promises
+ * @return a promise that is fulfilled with the value of the value of first promise to be fulfilled
+ */
+exports.first = function(array){
+ var deferred = new Deferred();
+ if(!(array instanceof Array)){
+ array = Array.prototype.slice.call(arguments);
+ }
+ var fulfilled;
+ array.forEach(function(promise, index){
+ exports.when(promise, function(value){
+ if (!fulfilled) {
+ fulfilled = true;
+ deferred.resolve(value);
+ }
+ },
+ function(error){
+ if (!fulfilled) {
+ fulfilled = true;
+ deferred.resolve(error);
+ }
+ });
+ });
+ return deferred.promise;
+};
+
+/**
+ * Takes an array of asynchronous functions (that return promises) and
+ * executes them sequentially. Each funtion is called with the return value of the last function
+ * @param array The array of function
+ * @param startingValue The value to pass to the first function
+ * @return the value returned from the last function
+ */
+exports.seq = function(array, startingValue){
+ array = array.concat(); // make a copy
+ var deferred = new Deferred();
+ function next(value){
+ var nextAction = array.shift();
+ if(nextAction){
+ exports.when(nextAction(value), next, deferred.reject);
+ }
+ else {
+ deferred.resolve(value);
+ }
+ }
+ next(startingValue);
+ return deferred.promise;
+};
+
+
+/**
+ * Delays for a given amount of time and then fulfills the returned promise.
+ * @param milliseconds The number of milliseconds to delay
+ * @return A promise that will be fulfilled after the delay
+ */
+if(typeof setTimeout !== "undefined") {
+ exports.delay = function(milliseconds) {
+ var deferred = new Deferred();
+ setTimeout(function(){
+ deferred.resolve();
+ }, milliseconds);
+ return deferred.promise;
+ };
+}
+
+
+
+/**
+ * Runs a function that takes a callback, but returns a Promise instead.
+ * @param func node compatible async function which takes a callback as its last argument
+ * @return promise for the return value from the callback from the function
+ */
+exports.execute = function(asyncFunction){
+ var args = Array.prototype.slice.call(arguments, 1);
+
+ var deferred = new Deferred();
+ args.push(function(error, result){
+ if(error) {
+ deferred.emitError(error);
+ }
+ else {
+ if(arguments.length > 2){
+ // if there are multiple success values, we return an array
+ Array.prototype.shift.call(arguments, 1);
+ deferred.emitSuccess(arguments);
+ }
+ else{
+ deferred.emitSuccess(result);
+ }
+ }
+ });
+ asyncFunction.apply(this, args);
+ return deferred.promise;
+};
+
+/**
+ * Converts a Node async function to a promise returning function
+ * @param func node compatible async function which takes a callback as its last argument
+ * @return A function that returns a promise
+ */
+exports.convertNodeAsyncFunction = function(asyncFunction, callbackNotDeclared){
+ var arity = asyncFunction.length;
+ if(callbackNotDeclared){
+ arity++;
+ }
+ return function(){
+ var deferred = new Deferred();
+ arguments.length = arity;
+ arguments[arity - 1] = function(error, result){
+ if(error) {
+ deferred.emitError(error);
+ }
+ else {
+ if(arguments.length > 2){
+ // if there are multiple success values, we return an array
+ Array.prototype.shift.call(arguments, 1);
+ deferred.emitSuccess(arguments);
+ }
+ else{
+ deferred.emitSuccess(result);
+ }
+ }
+ };
+ asyncFunction.apply(this, arguments);
+ return deferred.promise;
+ };
+};
50 lib/facet.js
View
@@ -63,7 +63,14 @@ function FacetedStore(store, facetClass){
for(var i in facetClass){
constructor[i] = facetClass[i];
}
- var needsOldVersion = true; // TODO: compute based on schema (if there are any readonly or blocked properties)
+ var needsOldVersion;
+ var properties = constructor.properties;
+ for(var i in properties){
+ var propDef = properties[i];
+ if(propDef.readonly || propDef.blocked){
+ needsOldVersion = true;
+ }
+ }
constructor.id = store.id;
constructor.query= function(query, options){
if(typeof facetClass.query !== "function"){
@@ -92,8 +99,10 @@ function FacetedStore(store, facetClass){
}
if(id.indexOf('.') > -1 && (id.indexOf('?') == -1 || id.indexOf('.') < id.indexOf('?'))){
var parts = id.split('.');
- var object = this.get(parts[0]);
- var value = object && (object.get ? object.get(parts[1]) : object[parts[1]]);
+ var value = this.get(parts[0]);
+ for(var i = 1; i < parts.length; i++){
+ value = value && (value.get ? value.get(parts[i]) : value[parts[i]]);
+ }
return value;
}
if(id === '' || id.match(/\?|\[/)){
@@ -126,15 +135,12 @@ function FacetedStore(store, facetClass){
}
return this.wrap(facetClass.get(id), this.transaction);
};
- function create(instance){
+ function create(instance, options){
instance = constructor.wrap({}, constructor.transaction, instance, NEW);
if(typeof instance.initialize === "function"){
instance.initialize.apply(instance, arguments);
}
- if(constructor.transaction){
- (constructor.transaction.newInstances = constructor.transaction.newInstances || []).push(instance);
- }
- return when(instance.save({overwrite:false}), function(){
+ return when(instance.save(options || {overwrite:false}), function(){
return instance;
});
};
@@ -192,8 +198,8 @@ function FacetedStore(store, facetClass){
}
};
- constructor.post = function(props, metadata){
- if(!metadata.id){
+ constructor.post = function(props, directives){
+ if(!directives.id){
// create a new object
return this.put(props, {overwrite: false});
}
@@ -202,17 +208,13 @@ function FacetedStore(store, facetClass){
// TODO: Do this: if(props instanceof RPC){ // where the media handler creates RPC objects
if("method" in props && "id" in props && "params" in props){
// looks like JSON-RPC
- return rpcInvoke(this.get(metadata.id), props);
+ return rpcInvoke(this.get(directives.id), props);
}
// doing an incremental update
- return this.copyProperties(props, metadata);
+ return this.copyProperties(props, directives);
}
};
- constructor.isNew= function(instance){
- return this.transaction.newInstances && this.transaction.newInstances.indexOf(instance) > -1;
- };
-
constructor.__proto__ = httpHandlerPrototype;
// TODO: handle immutable proto
@@ -342,8 +344,8 @@ var SchemaControlled = function(facetSchema, sourceClass){
enumerable: false
},
save: {
- value: function(metadata){
- metadata = metadata || {};
+ value: function(directives){
+ directives = directives || {};
if(facetPrototype.save){
facetPrototype.save.call(this, source);
}
@@ -360,16 +362,13 @@ var SchemaControlled = function(facetSchema, sourceClass){
}
}
mustBeValid(validation);
- if(transaction){
- var newIndex = transaction.newInstances && transaction.newInstances.indexOf(instance);
- }
try{
if(typeof facetSchema.put === "function"){
- var id = facetSchema.put(source, metadata);
+ var id = facetSchema.put(source, directives);
}
else{
if(facetSchema.__noSuchMethod__){
- var id = facetSchema.__noSuchMethod__("put", [source, metadata]);
+ var id = facetSchema.__noSuchMethod__("put", [source, directives]);
}
else{
throw new MethodNotAllowedError("put is not allowed");
@@ -385,9 +384,8 @@ var SchemaControlled = function(facetSchema, sourceClass){
});
}
finally{
- if(newIndex > -1){
- transaction.generatedId = source.getId ? source.getId() : source.id;
- transaction.newInstances.splice(newIndex, 1);
+ if(typeof id == "string" || typeof id == "number"){
+ transaction.generatedId = id;
}
}
function transfer(value){
4 lib/model.js
View
@@ -20,10 +20,6 @@ exports.Model = function(name, store, schema){
store = JSFile((require("settings").dataFolder || "data") + "/" + name);
}
store.id = name;
- if(!store.create){
- // map create to put in case it only implements the WebSimpleDB API
- store.create = store.put;
- }
schema.id = name;
schemas[name] = schema;
16 lib/store/memory.js
View
@@ -7,23 +7,25 @@ exports.Memory = function(options){
var store = ReadonlyMemory(options);
var uniqueKeys = {};
// start with the read-only memory store and add write support
- store.put = function(object, metadata){
- var id = object.id = metadata.id || object.id || Math.round(Math.random()*10000000000000);
- if("overwrite" in metadata){
- if(metadata.overwrite){
- if(!(id in this.index)){
+ store.put = function(object, directives){
+ directives = directives || {};
+ var id = object.id = directives.id || object.id || Math.round(Math.random()*10000000000000);
+ var isNew = !(id in this.index);
+ if("overwrite" in directives){
+ if(directives.overwrite){
+ if(isNew){
throw new PreconditionFailed(id + " does not exist to overwrite");
}
}
else{
- if(id in this.index){
+ if(!isNew){
throw new PreconditionFailed(id + " exists, and can't be overwritten");
}
}
}
updateIndexes.call(this, id, object);
this.index[id] = object;
- return id;
+ return isNew && id;
};
store["delete"] = function(id){
updateIndexes.call(this, id);
9 lib/store/sql.js
View
@@ -29,9 +29,9 @@ exports.SQLStore = function(config){
"delete": function(id){
store.executeSql("DELETE FROM " + config.table + " WHERE " + config.idColumn + "=?", [id]);
},
- put: function(object, metadata){
- id = metadata.id || object[config.idColumn];
- var overwrite = metadata.overwrite;
+ put: function(object, directives){
+ id = directives.id || object[config.idColumn];
+ var overwrite = directives.overwrite;
if(overwrite === undefined){
overwrite = this.get(id);
}
@@ -48,6 +48,7 @@ exports.SQLStore = function(config){
first = false;
}
}
+ params.idColumn = config.idColumn;
var results = store.executeSql("INSERT INTO " + config.table + " (" + columnsString + ") values (" + valuesPlacement + ")", params);
id = results.insertId;
object[config.idColumn] = id;
@@ -70,8 +71,6 @@ exports.SQLStore = function(config){
sql += " WHERE " + config.idColumn + "=?";
params.push(object[config.idColumn]);
store.executeSql(sql, params);
-
- return id;
},
query: function(query, options){
options = options || {};
Please sign in to comment.
Something went wrong with that request. Please try again.