diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3986ab2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules/ + +src/ +spec/ +**/*.map +*.md +LICENSE \ No newline at end of file diff --git a/js/Database.js b/js/Database.js new file mode 100644 index 0000000..f838f62 --- /dev/null +++ b/js/Database.js @@ -0,0 +1,140 @@ +// Generated by CoffeeScript 1.12.4 +var Database, Query, Table, assertType, define, isArray, isConstructor, methods, setProto, sliceArray, utils; + +isConstructor = require("isConstructor"); + +assertType = require("assertType"); + +sliceArray = require("sliceArray"); + +setProto = require("setProto"); + +Table = require("./Table"); + +Query = require("./Query"); + +utils = require("./utils"); + +isArray = Array.isArray; + +define = Object.defineProperty; + +Database = function(name) { + var r; + assertType(name, String); + r = function(value) { + return r.expr(value); + }; + r._name = name; + define(r, "_tables", { + value: {}, + writable: true + }); + return setProto(r, Database.prototype); +}; + +methods = {}; + +methods.init = function(tables) { + assertType(tables, Object); + this._tables = tables; +}; + +methods.load = function() { + var filePath, json; + filePath = require("path").resolve.apply(null, arguments); + json = require("fs").readFileSync(filePath, "utf8"); + this._tables = JSON.parse(json); +}; + +methods.table = function(tableId) { + if (tableId === void 0) { + throw Error("Cannot convert `undefined` with r.expr()"); + } + return Table(this, tableId); +}; + +methods.tableCreate = function(tableId) { + throw Error("Not implemented"); +}; + +methods.tableDrop = function(tableId) { + throw Error("Not implemented"); +}; + +methods.uuid = require("./utils/uuid"); + +methods.typeOf = function(value) { + if (arguments.length !== 1) { + throw Error("`typeOf` takes 1 argument, " + arguments.length + " provided"); + } + return Query._expr(value).typeOf(); +}; + +methods.branch = function(cond) { + var args; + args = sliceArray(arguments, 1); + if (args.length < 2) { + throw Error("`branch` takes at least 3 arguments, " + (args.length + 1) + " provided"); + } + return Query._branch(Query._expr(cond), args); +}; + +methods["do"] = function(arg) { + if (!arguments.length) { + throw Error("`do` takes at least 1 argument, 0 provided"); + } + return Query._do(Query._expr(arg), sliceArray(arguments, 1)); +}; + +methods.expr = Query._expr; + +methods.object = function() { + var args, query; + args = sliceArray(arguments); + if (args.length % 2) { + throw Error("Expected an even number of arguments"); + } + args.forEach(function(arg, index) { + if (arg === void 0) { + throw Error("Argument " + index + " to object may not be `undefined`"); + } + }); + query = Query(null, "DATUM"); + query._eval = function(ctx) { + var index, key, result; + result = {}; + index = 0; + while (index < args.length) { + key = utils.resolve(args[index]); + utils.expect(key, "STRING"); + result[key] = utils.resolve(args[index + 1]); + index += 2; + } + ctx.type = this._type; + return result; + }; + return query; +}; + +methods.asc = function(index) { + return { + ASC: true, + index: index + }; +}; + +methods.desc = function(index) { + return { + DESC: true, + index: index + }; +}; + +Object.keys(methods).forEach(function(key) { + return define(Database.prototype, key, { + value: methods[key] + }); +}); + +module.exports = Database; diff --git a/js/Query.js b/js/Query.js new file mode 100644 index 0000000..8b1a114 --- /dev/null +++ b/js/Query.js @@ -0,0 +1,462 @@ +// Generated by CoffeeScript 1.12.4 +var Query, Result, actions, define, getArity, getType, isArray, isArrayOrObject, isConstructor, isFalse, isNullError, methods, setProto, sliceArray, statics, utils, variadic; + +isConstructor = require("isConstructor"); + +sliceArray = require("sliceArray"); + +setProto = require("setProto"); + +actions = require("./actions"); + +Result = require("./Result"); + +utils = require("./utils"); + +isArray = Array.isArray; + +define = Object.defineProperty; + +Query = function(parent, type) { + var query; + query = function(key) { + return query.bracket(key); + }; + if (parent) { + query._db = parent._db; + query._type = type || parent._type; + query._parent = parent; + } else { + query._db = null; + query._type = type || null; + } + return setProto(query, Query.prototype); +}; + +variadic = function(keys) { + return keys.split(" ").forEach(function(key) { + methods[key] = function() { + return this._then(key, arguments); + }; + }); +}; + +methods = {}; + +methods["default"] = function(value) { + return Query._default(this, value); +}; + +methods["do"] = function() { + var args; + args = sliceArray(arguments); + return Query._do(this, args); +}; + +variadic("eq ne gt lt ge le or and add sub mul div"); + +methods.nth = function(index) { + return this._then("nth", arguments); +}; + +methods.bracket = function(key) { + return this._then("bracket", arguments); +}; + +methods.getField = function(field) { + return this._then("getField", arguments); +}; + +variadic("hasFields"); + +methods.offsetsOf = function(value) { + return this._then("offsetsOf", arguments); +}; + +methods.contains = function(value) { + return this._then("contains", arguments); +}; + +methods.orderBy = function(field) { + return this._then("orderBy", arguments); +}; + +methods.filter = function(filter, options) { + return this._then("filter", arguments); +}; + +methods.isEmpty = function() { + return this._then("isEmpty"); +}; + +methods.count = function() { + return this._then("count"); +}; + +methods.skip = function(count) { + return this._then("skip", arguments); +}; + +methods.limit = function(count) { + return this._then("limit", arguments); +}; + +variadic("slice merge pluck without"); + +methods.typeOf = function() { + return this._then("typeOf"); +}; + +methods.branch = function() { + var args; + args = sliceArray(arguments); + if (args.length < 2) { + throw Error("`branch` takes at least 2 arguments, " + args.length + " provided"); + } + return Query._branch(this, args); +}; + +methods.update = function(patch) { + return this._then("update", arguments); +}; + +methods.replace = function(values) { + return this._then("replace", arguments); +}; + +methods["delete"] = function() { + return this._then("delete"); +}; + +methods.run = function() { + return Promise.resolve().then(this._run.bind(this)); +}; + +methods.then = function(onFulfilled) { + return this.run().then(onFulfilled); +}; + +methods["catch"] = function(onRejected) { + return this.run()["catch"](onRejected); +}; + +methods._then = function(action, args) { + var query; + query = Query(this, getType(action)); + query._action = action; + if (args) { + query._args = args; + query._parseArgs(); + } + return query; +}; + +methods._parseArgs = function() { + var args, arity, index; + arity = getArity(this._action); + args = isArray(this._args) ? this._args : sliceArray(this._args); + if (args.length < arity[0]) { + throw Error("`" + this._action + "` takes at least " + arity[0] + " argument" + (arity[0] === 1 ? "" : "s") + ", " + args.length + " provided"); + } + if (args.length > arity[1]) { + throw Error("`" + this._action + "` takes at most " + arity[1] + " argument" + (arity[1] === 1 ? "" : "s") + ", " + args.length + " provided"); + } + index = -1; + while (++index < args.length) { + if (!utils.isQuery(args[index])) { + args[index] = Query._expr(args[index]); + } + } + this._args = args; +}; + +methods._eval = function(ctx) { + var action, args, arity, result; + action = this._action; + result = this._parent._eval(ctx); + if (isConstructor(action, Function)) { + return action.call(ctx, result); + } + if (isConstructor(action, String)) { + args = utils.resolve(this._args); + arity = getArity(action)[1]; + result = arity === 0 ? actions[action].call(ctx, result) : arity === 1 ? actions[action].call(ctx, result, args[0]) : arity === 2 ? actions[action].call(ctx, result, args[0], args[1]) : actions[action].call(ctx, result, args); + } + ctx.type = isConstructor(this._type, Function) ? this._type.call(this, ctx, args) : this._type; + return result; +}; + +methods._run = function(ctx) { + var result; + if (ctx == null) { + ctx = {}; + } + ctx.db = this._db; + result = this._eval(ctx); + if (/TABLE|SEQUENCE|SELECTION/.test(ctx.type)) { + return utils.clone(result); + } + return result; +}; + +statics = {}; + +statics._do = function(parent, args) { + var last, query, value; + if (!args.length) { + return parent; + } + query = Query(); + query._parent = parent; + last = args.pop(); + args.unshift(parent); + if (isConstructor(last, Function)) { + args = args.slice(0, last.length).map(Result); + value = last.apply(null, args); + if (value === void 0) { + throw Error("Anonymous function returned `undefined`. Did you forget a `return`?"); + } + if (!utils.isQuery(value)) { + value = Query._expr(value); + } + query._eval = function(ctx) { + var result; + result = value._eval(ctx); + args.forEach(function(arg) { + return arg._reset(); + }); + return result; + }; + return query; + } + query._eval = function(ctx) { + args.forEach(utils.resolve); + return utils.resolve(last, ctx); + }; + return query; +}; + +statics._default = function(parent, value) { + var query; + if (!utils.isQuery(value)) { + value = Query._expr(value); + } + query = Query(); + query._parent = parent; + query._eval = function(ctx) { + var error, result; + try { + result = parent._eval(ctx); + } catch (error1) { + error = error1; + if (!isNullError(error)) { + throw error; + } + } + return result != null ? result : value._eval(ctx); + }; + return query; +}; + +statics._branch = function(cond, args) { + var lastIndex, query; + if (args.length % 2) { + throw Error("`branch` cannot be called with an even number of arguments"); + } + lastIndex = args.length - 1; + query = Query(); + query._parent = cond; + query._eval = function(ctx) { + var index; + if (!isFalse(cond._eval({}))) { + return utils.resolve(args[0], ctx); + } + index = -1; + while ((index += 2) !== lastIndex) { + if (!isFalse(utils.resolve(args[index]))) { + return utils.resolve(args[index + 1], ctx); + } + } + return utils.resolve(args[lastIndex], ctx); + }; + return query; +}; + +statics._expr = function(expr) { + var query, values; + if (expr === void 0) { + throw Error("Cannot convert `undefined` with r.expr()"); + } + if (isConstructor(expr, Number) && !isFinite(expr)) { + throw Error("Cannot convert `" + expr + "` to JSON"); + } + if (utils.isQuery(expr)) { + return expr; + } + query = Query(null, "DATUM"); + if (isArrayOrObject(expr)) { + values = expr; + expr = isArray(values) ? [] : {}; + Object.keys(values).forEach(function(key) { + var value; + value = values[key]; + if (!utils.isQuery(value)) { + expr[key] = Query._expr(value); + return; + } + if (/DATUM|SELECTION/.test(value._type)) { + expr[key] = value; + return; + } + throw Error("Expected type DATUM but found " + value._type); + }); + query._eval = function(ctx) { + ctx.type = this._type; + return utils.resolve(expr); + }; + } else { + query._eval = function(ctx) { + ctx.type = this._type; + return expr; + }; + } + return query; +}; + +Object.keys(methods).forEach(function(key) { + return define(Query.prototype, key, { + value: methods[key], + writable: true + }); +}); + +Object.keys(statics).forEach(function(key) { + return define(Query, key, { + value: statics[key] + }); +}); + +module.exports = Query; + +isFalse = function(value) { + return (value === null) || (value === false); +}; + +isArrayOrObject = function(value) { + return isArray(value) || isConstructor(value, Object); +}; + +isNullError = function(error) { + return !error || /(Index out of bounds|No attribute|null)/i.test(error.message); +}; + +getType = (function() { + var DATUM, seqRE, sequential, types; + DATUM = "DATUM"; + seqRE = /TABLE|SEQUENCE/; + sequential = function(ctx) { + if (seqRE.test(ctx.type)) { + return "SEQUENCE"; + } + return DATUM; + }; + types = { + eq: DATUM, + ne: DATUM, + gt: DATUM, + lt: DATUM, + ge: DATUM, + le: DATUM, + or: DATUM, + and: DATUM, + add: DATUM, + sub: DATUM, + mul: DATUM, + div: DATUM, + nth: function(ctx) { + if (seqRE.test(ctx.type)) { + return "SELECTION"; + } + return DATUM; + }, + bracket: function(ctx, args) { + if (!isConstructor(args[0], String)) { + if (seqRE.test(ctx.type)) { + return "SELECTION"; + } + } + return DATUM; + }, + getField: DATUM, + hasFields: sequential, + offsetsOf: DATUM, + contains: DATUM, + orderBy: sequential, + filter: sequential, + fold: null, + isEmpty: DATUM, + count: DATUM, + skip: sequential, + limit: sequential, + slice: sequential, + merge: DATUM, + pluck: DATUM, + without: DATUM, + typeOf: DATUM, + update: DATUM, + replace: DATUM, + "delete": DATUM + }; + return function(action) { + return types[action]; + }; +})(); + +getArity = (function() { + var arity, none, one, onePlus, oneTwo, two; + none = [0, 0]; + one = [1, 1]; + two = [2, 2]; + oneTwo = [1, 2]; + onePlus = [1, 2e308]; + arity = { + eq: onePlus, + ne: onePlus, + gt: onePlus, + lt: onePlus, + ge: onePlus, + le: onePlus, + or: onePlus, + and: onePlus, + add: onePlus, + sub: onePlus, + mul: onePlus, + div: onePlus, + nth: one, + bracket: one, + getField: one, + hasFields: onePlus, + offsetsOf: one, + contains: one, + orderBy: one, + filter: oneTwo, + fold: two, + isEmpty: none, + count: none, + skip: one, + limit: one, + slice: onePlus, + merge: onePlus, + pluck: onePlus, + without: onePlus, + typeOf: none, + getAll: onePlus, + insert: oneTwo, + update: one, + replace: one, + "delete": none + }; + return function(action) { + return arity[action]; + }; +})(); diff --git a/js/Result.js b/js/Result.js new file mode 100644 index 0000000..a033d04 --- /dev/null +++ b/js/Result.js @@ -0,0 +1,49 @@ +// Generated by CoffeeScript 1.12.4 +var Result, define, evalQuery, methods, setProto, utils; + +setProto = require("setProto"); + +utils = require("./utils"); + +define = Object.defineProperty; + +Result = function(parent) { + var self; + self = function(key) { + return self.bracket(key); + }; + if (utils.isQuery(parent)) { + self._db = parent._db; + self._parent = parent; + } else { + self._db = null; + self._parent = Query._expr(parent); + } + return setProto(self, Result.prototype); +}; + +methods = {}; + +methods._eval = evalQuery = function(ctx) { + var result; + result = this._parent._run(); + this._eval = function(ctx) { + ctx.type = "DATUM"; + return result; + }; + ctx.type = "DATUM"; + return result; +}; + +methods._reset = function() { + delete this._eval; +}; + +Object.keys(methods).forEach(function(key) { + return define(Result.prototype, key, { + value: methods[key], + writable: true + }); +}); + +module.exports = Result; diff --git a/js/Table.js b/js/Table.js new file mode 100644 index 0000000..a971c10 --- /dev/null +++ b/js/Table.js @@ -0,0 +1,242 @@ +// Generated by CoffeeScript 1.12.4 +var Query, Table, assertType, clearTable, define, findRow, getRow, getRows, insertRows, isArray, isConstructor, methods, parseArgs, runQuery, setProto, sliceArray, utils, uuid; + +isConstructor = require("isConstructor"); + +assertType = require("assertType"); + +sliceArray = require("sliceArray"); + +setProto = require("setProto"); + +Query = require("./Query"); + +utils = require("./utils"); + +uuid = require("./utils/uuid"); + +isArray = Array.isArray; + +parseArgs = Query.prototype._parseArgs; + +runQuery = Query.prototype._run; + +define = Object.defineProperty; + +Table = function(db, tableId) { + var query; + query = function(key) { + return query.bracket(key); + }; + query._db = db; + query._type = "TABLE"; + query._tableId = tableId; + return setProto(query, Table.prototype); +}; + +methods = {}; + +methods["do"] = function(callback) { + throw Error("Tables must be coerced to arrays before calling `do`"); +}; + +methods.get = function(rowId) { + var self; + if (rowId === void 0) { + throw Error("Cannot convert `undefined` with r.expr()"); + } + self = Table(this._db, this._tableId); + self._action = "get"; + self._rowId = rowId; + return Query(self, "SELECTION"); +}; + +methods.getAll = function() { + var self; + self = Table(this._db, this._tableId); + self._action = "getAll"; + self._args = arguments; + parseArgs.call(self); + return Query(self, "SEQUENCE"); +}; + +methods.insert = function(rows, options) { + var self; + self = Table(this._db, this._tableId); + self._action = "insert"; + self._args = arguments; + parseArgs.call(self); + return Query(self, "DATUM"); +}; + +methods["delete"] = function() { + var self; + self = Table(this._db, this._tableId); + self._action = "delete"; + return Query(self, "DATUM"); +}; + +"nth bracket getField offsetsOf contains orderBy filter fold count limit slice merge pluck without update".split(" ").forEach(function(key) { + methods[key] = function() { + return Query(this, "TABLE")._then(key, arguments); + }; +}); + +methods.run = function() { + return Promise.resolve().then(runQuery.bind(this)); +}; + +methods.then = function(onFulfilled) { + return this.run().then(onFulfilled); +}; + +methods._eval = function(ctx) { + var args, table; + ctx.type = this._type; + ctx.tableId = this._tableId; + if (!(table = this._db._tables[this._tableId])) { + throw Error("Table `" + this._tableId + "` does not exist"); + } + if (!this._action) { + return table; + } + args = utils.resolve(this._args); + switch (this._action) { + case "get": + return getRow(table, this._rowId, ctx); + case "getAll": + return getRows(table, args); + case "insert": + return insertRows(table, args[0], args[1]); + case "delete": + return clearTable(table); + } +}; + +methods._run = runQuery; + +Object.keys(methods).forEach(function(key) { + return define(Table.prototype, key, { + value: methods[key], + writable: true + }); +}); + +module.exports = Table; + +getRow = function(table, rowId, ctx) { + var index; + if (rowId === void 0) { + throw Error("Argument 1 to get may not be `undefined`"); + } + if (utils.isQuery(rowId)) { + rowId = rowId._run(); + } + if ((rowId === null) || isConstructor(rowId, Object)) { + throw Error("Primary keys must be either a number, string, bool, pseudotype or array"); + } + ctx.rowId = rowId; + ctx.rowIndex = -1; + index = -1; + while (++index < table.length) { + if (table[index].id === rowId) { + ctx.rowIndex = index; + return table[index]; + } + } + return null; +}; + +getRows = function(table, args) { + var key; + if (!args.length) { + return []; + } + if (isConstructor(args[args.length - 1], Object)) { + key = args.pop().index; + } + if (key == null) { + key = "id"; + } + utils.expect(key, "STRING"); + args.forEach(function(arg, index) { + if (arg === null) { + throw Error("Keys cannot be NULL"); + } + if (isConstructor(arg, Object)) { + throw Error((key === "id" ? "Primary" : "Secondary") + " keys must be either a number, string, bool, pseudotype or array"); + } + }); + return table.filter(function(row) { + var arg, i, len; + for (i = 0, len = args.length; i < len; i++) { + arg = args[i]; + if (isArray(arg)) { + if (utils.equals(arg, row[key])) { + return true; + } + } else if (arg === row[key]) { + return true; + } + } + return false; + }); +}; + +insertRows = function(table, rows) { + var errors, generated_keys, i, len, res, row; + if (!isArray(rows)) { + rows = [rows]; + } + errors = 0; + generated_keys = []; + for (i = 0, len = rows.length; i < len; i++) { + row = rows[i]; + assertType(row, Object); + if (row.hasOwnProperty("id")) { + if (findRow(table, row.id)) { + errors += 1; + } else { + table.push(row); + } + } else { + generated_keys.push(row.id = uuid()); + table.push(row); + } + } + res = { + errors: errors + }; + if (errors > 0) { + res.first_error = "Duplicate primary key `id`"; + } + res.inserted = rows.length - errors; + if (generated_keys.length) { + res.generated_keys = generated_keys; + } + return res; +}; + +findRow = function(table, rowId) { + if (rowId === void 0) { + throw Error("Argument 1 to get may not be `undefined`"); + } + if (utils.isQuery(rowId)) { + rowId = rowId._run(); + } + if ((rowId === null) || isConstructor(rowId, Object)) { + throw Error("Primary keys must be either a number, string, bool, pseudotype or array"); + } + return table.find(function(row) { + return row.id === rowId; + }); +}; + +clearTable = function(table) { + var deleted; + deleted = table.length; + table.length = 0; + return { + deleted: deleted + }; +}; diff --git a/js/actions.js b/js/actions.js new file mode 100644 index 0000000..65dd7f3 --- /dev/null +++ b/js/actions.js @@ -0,0 +1,575 @@ +// Generated by CoffeeScript 1.12.4 +var actions, deleteRow, deleteRows, equals, isArray, isConstructor, isFalse, seq, seqRE, sortAscending, sortDescending, updateRow, updateRows, utils; + +isConstructor = require("isConstructor"); + +utils = require("./utils"); + +seq = require("./utils/seq"); + +isArray = Array.isArray; + +seqRE = /TABLE|SEQUENCE/; + +actions = exports; + +actions.eq = function(result, args) { + return equals(result, args); +}; + +actions.ne = function(result, args) { + return !equals(result, args); +}; + +actions.gt = function(result, args) { + var arg, i, len, prev; + prev = result; + for (i = 0, len = args.length; i < len; i++) { + arg = args[i]; + if (prev <= arg) { + return false; + } + prev = arg; + } + return true; +}; + +actions.lt = function(result, args) { + var arg, i, len, prev; + prev = result; + for (i = 0, len = args.length; i < len; i++) { + arg = args[i]; + if (prev >= arg) { + return false; + } + prev = arg; + } + return true; +}; + +actions.ge = function(result, args) { + var arg, i, len, prev; + prev = result; + for (i = 0, len = args.length; i < len; i++) { + arg = args[i]; + if (prev < arg) { + return false; + } + prev = arg; + } + return true; +}; + +actions.le = function(result, args) { + var arg, i, len, prev; + prev = result; + for (i = 0, len = args.length; i < len; i++) { + arg = args[i]; + if (prev > arg) { + return false; + } + prev = arg; + } + return true; +}; + +actions.or = function(result, args) { + var arg, i, len; + if (!isFalse(result)) { + return result; + } + for (i = 0, len = args.length; i < len; i++) { + arg = args[i]; + if (!isFalse(arg)) { + return arg; + } + } + return args.pop(); +}; + +actions.and = function(result, args) { + var arg, i, len; + if (isFalse(result)) { + return result; + } + for (i = 0, len = args.length; i < len; i++) { + arg = args[i]; + if (isFalse(arg)) { + return arg; + } + } + return args.pop(); +}; + +actions.add = function(result, args) { + var arg, i, len, total, type; + type = utils.typeOf(result); + if (!/ARRAY|NUMBER|STRING/.test(type)) { + throw Error("Expected type ARRAY, NUMBER, or STRING but found " + type); + } + total = result; + for (i = 0, len = args.length; i < len; i++) { + arg = args[i]; + utils.expect(arg, type); + if (type === "ARRAY") { + total = total.concat(arg); + } else { + total += arg; + } + } + return total; +}; + +actions.sub = function(result, args) { + var arg, i, len, total; + utils.expect(result, "NUMBER"); + total = result; + for (i = 0, len = args.length; i < len; i++) { + arg = args[i]; + utils.expect(arg, "NUMBER"); + total -= arg; + } + return null; +}; + +actions.mul = function(result, args) { + var arg, i, len, total; + utils.expect(result, "NUMBER"); + total = result; + for (i = 0, len = args.length; i < len; i++) { + arg = args[i]; + utils.expect(arg, "NUMBER"); + total *= arg; + } + return null; +}; + +actions.div = function(result, args) { + var arg, i, len, total; + utils.expect(result, "NUMBER"); + total = result; + for (i = 0, len = args.length; i < len; i++) { + arg = args[i]; + utils.expect(arg, "NUMBER"); + total /= arg; + } + return null; +}; + +actions.nth = function(result, index) { + utils.expect(result, "ARRAY"); + utils.expect(index, "NUMBER"); + if (index < -1 && seqRE.test(this.type)) { + throw Error("Cannot use an index < -1 on a stream"); + } + return seq.nth(result, index); +}; + +actions.bracket = function(result, key) { + var type; + type = utils.typeOf(key); + if (type === "NUMBER") { + if (key < -1 && seqRE.test(this.type)) { + throw Error("Cannot use an index < -1 on a stream"); + } + return seq.nth(result, key); + } + if (type !== "STRING") { + throw Error("Expected NUMBER or STRING as second argument to `bracket` but found " + type); + } + type = utils.typeOf(result); + if (type === "ARRAY") { + return seq.getField(result, key); + } + if (type === "OBJECT") { + return utils.getField(result, key); + } + throw Error("Expected ARRAY or OBJECT as first argument to `bracket` but found " + type); +}; + +actions.getField = function(result, attr) { + var type; + utils.expect(attr, "STRING"); + type = utils.typeOf(result); + if (type === "ARRAY") { + return seq.getField(result, attr); + } + if (type === "OBJECT") { + return utils.getField(result, attr); + } + throw Error("Expected ARRAY or OBJECT but found " + type); +}; + +actions.hasFields = function(result, attrs) { + var attr, i, len, type; + attrs = utils.flatten(attrs); + for (i = 0, len = attrs.length; i < len; i++) { + attr = attrs[i]; + utils.expect(attr, "STRING"); + } + type = utils.typeOf(result); + if (type === "ARRAY") { + return seq.hasFields(result, attrs); + } + if (type === "OBJECT") { + return utils.hasFields(result, attrs); + } + throw Error("Expected ARRAY or OBJECT but found " + type); +}; + +actions.offsetsOf = function(array, value) { + var i, index, len, offsets, value2; + utils.expect(array, "ARRAY"); + if (isConstructor(value, Function)) { + throw Error("Function argument not yet implemented"); + } + offsets = []; + for (index = i = 0, len = array.length; i < len; index = ++i) { + value2 = array[index]; + if (utils.equals(value2, value)) { + offsets.push(index); + } + } + return offsets; +}; + +actions.contains = function(array, value) { + var i, len, value2; + utils.expect(array, "ARRAY"); + if (isConstructor(value, Function)) { + throw Error("Function argument not yet implemented"); + } + for (i = 0, len = array.length; i < len; i++) { + value2 = array[i]; + if (utils.equals(value, value2)) { + return true; + } + } + return false; +}; + +actions.orderBy = function(array, value) { + var DESC, index, sorter; + utils.expect(array, "ARRAY"); + if (isConstructor(value, Object)) { + DESC = value.DESC, index = value.index; + } else if (isConstructor(value, String)) { + index = value; + } + utils.expect(index, "STRING"); + sorter = DESC ? sortDescending(index) : sortAscending(index); + return array.slice().sort(sorter); +}; + +actions.filter = function(array, filter, options) { + var matchers; + utils.expect(array, "ARRAY"); + if (options !== void 0) { + utils.expect(options, "OBJECT"); + } + matchers = []; + if (isConstructor(filter, Object)) { + matchers.push(function(values) { + utils.expect(values, "OBJECT"); + return true; + }); + Object.keys(filter).forEach(function(key) { + return matchers.push(function(values) { + return utils.equals(values[key], filter[key]); + }); + }); + } else if (isConstructor(filter, Function)) { + throw Error("Filter functions are not implemented yet"); + } else { + return array; + } + return array.filter(function(row) { + var i, len, matcher; + for (i = 0, len = matchers.length; i < len; i++) { + matcher = matchers[i]; + if (!matcher(row)) { + return false; + } + } + return true; + }); +}; + +actions.fold = function() { + throw Error("Not implemented"); +}; + +actions.isEmpty = function(array) { + utils.expect(array, "ARRAY"); + return array.length === 0; +}; + +actions.count = function(array) { + utils.expect(array, "ARRAY"); + return array.length; +}; + +actions.skip = function(array, count) { + utils.expect(array, "ARRAY"); + utils.expect(count, "NUMBER"); + if (count < 0 && seqRE.test(this.type)) { + throw Error("Cannot use a negative left index on a stream"); + } + return array.slice(count); +}; + +actions.limit = function(array, count) { + utils.expect(array, "ARRAY"); + utils.expect(count, "NUMBER"); + if (count < 0) { + throw Error("LIMIT takes a non-negative argument"); + } + return array.slice(0, count); +}; + +actions.slice = function(result, args) { + var type; + type = utils.typeOf(result); + if (type === "ARRAY") { + return seq.slice(result, args); + } + if (type === "BINARY") { + throw Error("`slice` does not support BINARY values (yet)"); + } + if (type === "STRING") { + throw Error("`slice` does not support STRING values (yet)"); + } + throw Error("Expected ARRAY, BINARY, or STRING, but found " + type); +}; + +actions.merge = function(result, args) { + var type; + type = utils.typeOf(result); + if (type === "ARRAY") { + return seq.merge(result, args); + } + if (type === "OBJECT") { + return utils.merge(result, args); + } + throw Error("Expected ARRAY or OBJECT but found " + type); +}; + +actions.pluck = function(result, args) { + var type; + type = utils.typeOf(result); + if (type === "ARRAY") { + return seq.pluck(result, args); + } + if (type === "OBJECT") { + return utils.pluck(result, args); + } + throw Error("Expected ARRAY or OBJECT but found " + type); +}; + +actions.without = function(result, args) { + var type; + args = utils.flatten(args); + type = utils.typeOf(result); + if (type === "ARRAY") { + return seq.without(result, args); + } + if (type === "OBJECT") { + return utils.without(result, args); + } + throw Error("Expected ARRAY or OBJECT but found " + type); +}; + +actions.typeOf = utils.typeOf; + +actions.update = function(result, patch) { + if (isArray(result)) { + return updateRows.call(this, result, patch); + } + return updateRow.call(this, result, patch); +}; + +actions.replace = function(row, values) { + var table; + if (this.type !== "SELECTION") { + throw Error("Expected type SELECTION but found " + this.type); + } + table = this.db._tables[this.tableId]; + if (values === null) { + if (row === null) { + return { + deleted: 0, + skipped: 1 + }; + } + table.splice(this.rowIndex, 1); + return { + deleted: 1, + skipped: 0 + }; + } + if ("OBJECT" !== utils.typeOf(values)) { + throw Error("Inserted value must be an OBJECT (got " + (utils.typeOf(values)) + ")"); + } + if (!values.hasOwnProperty("id")) { + throw Error("Inserted object must have primary key `id`"); + } + if (values.id !== this.rowId) { + throw Error("Primary key `id` cannot be changed"); + } + if (row === null) { + table.push(utils.clone(values)); + return { + inserted: 1 + }; + } + if (utils.equals(row, values)) { + return { + replaced: 0, + unchanged: 1 + }; + } + table[this.rowIndex] = utils.clone(values); + return { + replaced: 1, + unchanged: 0 + }; +}; + +actions["delete"] = function(result) { + if (isArray(result)) { + return deleteRows.call(this, result); + } + return deleteRow.call(this, result); +}; + +equals = function(result, args) { + var arg, i, len; + for (i = 0, len = args.length; i < len; i++) { + arg = args[i]; + if (!utils.equals(result, arg)) { + return false; + } + } + return true; +}; + +isFalse = function(value) { + return (value === null) || (value === false); +}; + +sortAscending = function(index) { + return function(a, b) { + if (b[index] === void 0) { + return 1; + } + if (a[index] > b[index]) { + return 1; + } + return -1; + }; +}; + +sortDescending = function(index) { + return function(a, b) { + if (b[index] === void 0) { + return -1; + } + if (a[index] >= b[index]) { + return -1; + } + return 1; + }; +}; + +updateRows = function(rows, patch) { + var i, len, replaced, row; + if (this.type === "DATUM") { + throw Error("Expected type SEQUENCE but found DATUM"); + } + if (!rows.length) { + return { + replaced: 0, + unchanged: 0 + }; + } + if (patch === null) { + return { + replaced: 0, + unchanged: rows.length + }; + } + utils.expect(patch, "OBJECT"); + replaced = 0; + for (i = 0, len = rows.length; i < len; i++) { + row = rows[i]; + if (utils.update(row, patch)) { + replaced += 1; + } + } + return { + replaced: replaced, + unchanged: rows.length - replaced + }; +}; + +updateRow = function(row, patch) { + if (this.type !== "SELECTION") { + throw Error("Expected type SELECTION but found " + this.type); + } + if (row === null) { + return { + replaced: 0, + skipped: 1 + }; + } + if (utils.update(row, patch)) { + return { + replaced: 1, + unchanged: 0 + }; + } + return { + replaced: 0, + unchanged: 1 + }; +}; + +deleteRows = function(rows) { + var deleted; + if (this.type !== "SEQUENCE") { + throw Error("Expected type SEQUENCE but found " + this.type); + } + if (!rows.length) { + return { + deleted: 0 + }; + } + deleted = 0; + this.db._tables[this.tableId] = this.db._tables[this.tableId].filter(function(row) { + if (~rows.indexOf(row)) { + deleted += 1; + return false; + } + return true; + }); + return { + deleted: deleted + }; +}; + +deleteRow = function(row) { + if (row === null) { + return { + deleted: 0, + skipped: 1 + }; + } + if (this.type !== "SELECTION") { + throw Error("Expected type SELECTION but found " + this.type); + } + this.db._tables[this.tableId].splice(this.rowIndex, 1); + return { + deleted: 1, + skipped: 0 + }; +}; diff --git a/js/index.js b/js/index.js new file mode 100644 index 0000000..45c4f48 --- /dev/null +++ b/js/index.js @@ -0,0 +1,37 @@ +// Generated by CoffeeScript 1.12.4 +var Database, Query, Table, assertType, cache, rethinkdb, setProto, utils; + +assertType = require("assertType"); + +setProto = require("setProto"); + +Database = require("./Database"); + +Table = require("./Table"); + +Query = require("./Query"); + +utils = require("./utils"); + +setProto(require("./Result").prototype, Query.prototype); + +utils.isQuery = utils.isQuery.bind(null, [Query, Table]); + +cache = Object.create(null); + +rethinkdb = function(options) { + var db, name; + if (options == null) { + options = {}; + } + assertType(options, Object); + name = options.name || "test"; + if (db = cache[name]) { + return db; + } + db = Database(name); + cache[name] = db; + return db; +}; + +module.exports = rethinkdb; diff --git a/js/utils/index.js b/js/utils/index.js new file mode 100644 index 0000000..5a1329a --- /dev/null +++ b/js/utils/index.js @@ -0,0 +1,384 @@ +// Generated by CoffeeScript 1.12.4 +var arrayEquals, assertType, hasKeys, inherits, isArray, isArrayOrObject, isConstructor, merge, objectEquals, pluckWithArray, pluckWithObject, resolveArray, resolveObject, sliceArray, typeNames, update, utils; + +isConstructor = require("isConstructor"); + +assertType = require("assertType"); + +sliceArray = require("sliceArray"); + +hasKeys = require("hasKeys"); + +isArray = Array.isArray; + +typeNames = { + boolean: "BOOL", + number: "NUMBER", + object: "OBJECT", + string: "STRING" +}; + +utils = exports; + +utils.typeOf = function(value) { + var name; + if (value === null) { + return "NULL"; + } + if (isArray(value)) { + return "ARRAY"; + } + if (name = typeNames[typeof value]) { + return name; + } + throw Error("Unsupported value type"); +}; + +utils.expect = function(value, expectedType) { + var type; + type = utils.typeOf(value); + if (type !== expectedType) { + throw Error("Expected type " + expectedType + " but found " + type); + } +}; + +utils.isQuery = function(queryTypes, value) { + return value && inherits(value, queryTypes); +}; + +utils.getField = function(value, attr) { + if (value.hasOwnProperty(attr)) { + return value[attr]; + } + throw Error("No attribute `" + attr + "` in object"); +}; + +utils.hasFields = function(value, attrs) { + var attr, i, len; + for (i = 0, len = attrs.length; i < len; i++) { + attr = attrs[i]; + if (!value.hasOwnProperty(attr)) { + return false; + } + } + return true; +}; + +utils.equals = function(value1, value2) { + if (isArray(value1)) { + if (!isArray(value2)) { + return false; + } + return arrayEquals(value1, value2); + } + if (isConstructor(value1, Object)) { + if (!isConstructor(value2, Object)) { + return false; + } + return objectEquals(value1, value2); + } + return value1 === value2; +}; + +utils.flatten = function(input, output) { + var i, len, value; + if (output == null) { + output = []; + } + assertType(input, Array); + assertType(output, Array); + for (i = 0, len = input.length; i < len; i++) { + value = input[i]; + if (isArray(value)) { + utils.flatten(value, output); + } else { + output.push(value); + } + } + return output; +}; + +utils.pluck = function(input, keys) { + return pluckWithArray(keys, input, {}); +}; + +utils.without = function(input, keys) { + var key, output, value; + output = {}; + for (key in input) { + value = input[key]; + if (!~keys.indexOf(key)) { + output[key] = value; + } + } + return output; +}; + +utils.merge = function(output, inputs) { + var i, input, len; + output = utils.clone(output); + for (i = 0, len = inputs.length; i < len; i++) { + input = inputs[i]; + output = merge(output, input); + } + if (isArray(output)) { + return utils.resolve(output); + } + return output; +}; + +utils.update = function(object, patch) { + if (patch === null) { + return false; + } + if ("OBJECT" !== utils.typeOf(patch)) { + throw Error("Inserted value must be an OBJECT (got " + (utils.typeOf(patch)) + ")"); + } + if (patch.hasOwnProperty("id")) { + if (patch.id !== object.id) { + throw Error("Primary key `id` cannot be changed"); + } + } + return !!update(object, patch); +}; + +utils.clone = function(values) { + var clone, key, value; + if (values === null) { + return null; + } + if (isArray(values)) { + return values.map(function(value) { + if (isArrayOrObject(value)) { + return utils.clone(value); + } else { + return value; + } + }); + } + clone = {}; + for (key in values) { + value = values[key]; + clone[key] = isArrayOrObject(value) ? utils.clone(value) : value; + } + return clone; +}; + +utils.resolve = function(value, ctx) { + if (utils.isQuery(value)) { + return value._run(ctx); + } + if (ctx != null) { + ctx.type = "DATUM"; + } + if (isArray(value)) { + return resolveArray(value); + } + if (isConstructor(value, Object)) { + return resolveObject(value); + } + return value; +}; + +inherits = function(value, types) { + var i, len, type; + for (i = 0, len = types.length; i < len; i++) { + type = types[i]; + if (value instanceof type) { + return true; + } + } + return false; +}; + +isArrayOrObject = function(value) { + return isArray(value) || isConstructor(value, Object); +}; + +arrayEquals = function(array1, array2) { + var i, index, len, value1; + if (array1.length !== array2.length) { + return false; + } + for (index = i = 0, len = array1.length; i < len; index = ++i) { + value1 = array1[index]; + if (!utils.equals(value1, array2[index])) { + return false; + } + } + return true; +}; + +objectEquals = function(object1, object2) { + var i, j, key, keys, len, len1, ref; + keys = Object.keys(object1); + ref = Object.keys(object2); + for (i = 0, len = ref.length; i < len; i++) { + key = ref[i]; + if (!~keys.indexOf(key)) { + return false; + } + } + for (j = 0, len1 = keys.length; j < len1; j++) { + key = keys[j]; + if (!utils.equals(object1[key], object2[key])) { + return false; + } + } + return true; +}; + +pluckWithArray = function(array, input, output) { + var i, key, len; + array = utils.flatten(array); + for (i = 0, len = array.length; i < len; i++) { + key = array[i]; + if (isConstructor(key, String)) { + if (input.hasOwnProperty(key)) { + output[key] = input[key]; + } + } else if (isConstructor(key, Object)) { + pluckWithObject(key, input, output); + } else { + throw TypeError("Invalid path argument"); + } + } + return output; +}; + +pluckWithObject = function(object, input, output) { + var key, value; + for (key in object) { + value = object[key]; + if (value === true) { + if (input.hasOwnProperty(key)) { + output[key] = input[key]; + } + } else if (isConstructor(value, String)) { + if (!isConstructor(input[key], Object)) { + continue; + } + if (!input[key].hasOwnProperty(value)) { + continue; + } + if (!isConstructor(output[key], Object)) { + output[key] = {}; + } + output[key][value] = input[key][value]; + } else if (isArray(value)) { + if (!isConstructor(input[key], Object)) { + continue; + } + if (isConstructor(output[key], Object)) { + pluckWithArray(value, input[key], output[key]); + } else { + value = pluckWithArray(value, input[key], {}); + if (hasKeys(value)) { + output[key] = value; + } + } + } else if (isConstructor(value, Object)) { + if (!isConstructor(input[key], Object)) { + continue; + } + if (isConstructor(output[key], Object)) { + pluckWithObject(value, input[key], output[key]); + } else { + value = pluckWithObject(value, input[key], {}); + if (hasKeys(value)) { + output[key] = value; + } + } + } else { + throw TypeError("Invalid path argument"); + } + } + return output; +}; + +merge = function(output, input) { + var key, value; + if (!isConstructor(input, Object)) { + return input; + } + if (!isConstructor(output, Object)) { + return input; + } + for (key in input) { + value = input[key]; + if (isConstructor(value, Object)) { + if (isConstructor(output[key], Object)) { + merge(output[key], value); + } else { + output[key] = value; + } + } else { + output[key] = value; + } + } + return output; +}; + +update = function(output, input) { + var changes, key, value; + changes = 0; + for (key in input) { + value = input[key]; + if (isConstructor(value, Object)) { + if (!isConstructor(output[key], Object)) { + changes += 1; + output[key] = utils.clone(value); + continue; + } + changes += update(output[key], value); + } else if (isArray(value)) { + if (isArray(output[key])) { + if (arrayEquals(value, output[key])) { + continue; + } + } + changes += 1; + output[key] = utils.clone(value); + } else if (value !== output[key]) { + changes += 1; + output[key] = value; + } + } + return changes; +}; + +resolveArray = function(values) { + var clone, i, index, len, value; + clone = []; + for (index = i = 0, len = values.length; i < len; index = ++i) { + value = values[index]; + if (isArray(value)) { + clone.push(resolveArray(value)); + } else if (isConstructor(value, Object)) { + clone.push(resolveObject(value)); + } else if (utils.isQuery(value)) { + clone.push(value._run()); + } else { + clone.push(value); + } + } + return clone; +}; + +resolveObject = function(values) { + var clone, key, value; + clone = {}; + for (key in values) { + value = values[key]; + if (isArray(value)) { + clone[key] = resolveArray(value); + } else if (isConstructor(value, Object)) { + clone[key] = resolveObject(value); + } else if (utils.isQuery(value)) { + clone[key] = value._run(); + } else { + clone[key] = value; + } + } + return clone; +}; diff --git a/js/utils/seq.js b/js/utils/seq.js new file mode 100644 index 0000000..f163479 --- /dev/null +++ b/js/utils/seq.js @@ -0,0 +1,83 @@ +// Generated by CoffeeScript 1.12.4 +var isArray, isConstructor, seq, utils; + +isConstructor = require("isConstructor"); + +utils = require("."); + +isArray = Array.isArray; + +seq = exports; + +seq.nth = function(array, index) { + if (index < 0) { + index = array.length + index; + } + if (index < 0 || index >= array.length) { + throw RangeError("Index out of bounds"); + } + return array[index]; +}; + +seq.getField = function(array, attr) { + var i, len, results, value; + results = []; + for (i = 0, len = array.length; i < len; i++) { + value = array[i]; + utils.expect(value, "OBJECT"); + if (value.hasOwnProperty(attr)) { + results.push(value[attr]); + } + } + return results; +}; + +seq.hasFields = function(array, attrs) { + var i, len, results, value; + results = []; + for (i = 0, len = array.length; i < len; i++) { + value = array[i]; + utils.expect(value, "OBJECT"); + if (utils.hasFields(value, attrs)) { + results.push(value); + } + } + return results; +}; + +seq.slice = function(array, args) { + var endIndex, options, startIndex; + options = isConstructor(args[args.length - 1], Object) ? args.pop() : {}; + startIndex = args[0], endIndex = args[1]; + if (endIndex == null) { + endIndex = array.length; + } + utils.expect(startIndex, "NUMBER"); + utils.expect(endIndex, "NUMBER"); + if (options.leftBound === "open") { + startIndex += 1; + } + if (options.rightBound === "closed") { + endIndex += 1; + } + return array.slice(startIndex, endIndex); +}; + +seq.merge = function(rows, args) { + return rows.map(function(row) { + utils.expect(row, "OBJECT"); + return utils.merge(row, args); + }); +}; + +seq.pluck = function(rows, args) { + return rows.map(function(row) { + return utils.pluck(row, args); + }); +}; + +seq.without = function(rows, args) { + return rows.map(function(row) { + return utils.without(row, args); + }); +}; diff --git a/js/utils/uuid.js b/js/utils/uuid.js new file mode 100644 index 0000000..7cfb99e --- /dev/null +++ b/js/utils/uuid.js @@ -0,0 +1,18 @@ +// Generated by CoffeeScript 1.12.4 +var assertType, namespace, uuid_v4, uuid_v5; + +assertType = require("assertType"); + +uuid_v4 = require("uuid/v4"); + +uuid_v5 = require("uuid/v5"); + +namespace = "91461c99-f89d-49d2-af96-d8e2e14e9b58"; + +module.exports = function(value) { + if (arguments.length) { + assertType(value, String); + return uuid_v5(value, namespace); + } + return uuid_v4(); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..321dbf6 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "rethinkdb-mock", + "version": "0.5.0", + "main": "js/index", + "dependencies": { + "assertType": "aleclarson/assertType#2.0.2", + "hasKeys": "aleclarson/hasKeys#1.0.0", + "isConstructor": "aleclarson/isConstructor#1.0.0", + "setProto": "aleclarson/setProto#1.0.1", + "sliceArray": "aleclarson/sliceArray#1.0.0", + "uuid": "^3.1.0" + } +}