updraft
Version:
Javascript ORM-like storage in SQLite (WebSQL or other), synced to the cloud
1,116 lines • 90.9 kB
JavaScript
var Updraft;
(function (Updraft) {
/* istanbul ignore next */
function toObject(val) {
if (val === null || val === undefined) {
throw new TypeError("Object.assign cannot be called with null or undefined");
}
return Object(val);
}
/* istanbul ignore next */
var ObjectAssign = Object.assign || function (target, source) {
var hasOwnProperty = Object.prototype.hasOwnProperty;
var propIsEnumerable = Object.prototype.propertyIsEnumerable;
var from;
var to = toObject(target);
var symbols;
for (var s = 1; s < arguments.length; s++) {
from = Object(arguments[s]);
for (var key in from) {
if (hasOwnProperty.call(from, key)) {
to[key] = from[key];
}
}
if (Object.getOwnPropertySymbols) {
symbols = Object.getOwnPropertySymbols(from);
for (var i = 0; i < symbols.length; i++) {
if (propIsEnumerable.call(from, symbols[i])) {
to[symbols[i]] = from[symbols[i]];
}
}
}
}
return to;
};
Updraft.assign = ObjectAssign;
})(Updraft || (/* istanbul ignore next */ Updraft = {}));
var Updraft;
(function (Updraft) {
function reviver(key, value) {
if (typeof value === "string") {
var regexp = /^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\d\d\dZ$/.exec(value);
if (regexp) {
return new Date(value);
}
}
return value;
}
function toText(o) {
return JSON.stringify(o);
}
Updraft.toText = toText;
function fromText(text) {
return JSON.parse(text, reviver);
}
Updraft.fromText = fromText;
})(Updraft || (/* istanbul ignore next */ Updraft = {}));
var Updraft;
(function (Updraft) {
/* istanbul ignore next */
function makePrintable(x) {
if (Array.isArray(x) || (x && typeof x === "object")) {
return JSON.stringify(x);
}
else {
return x;
}
}
/**
* 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.
*/
function verify(condition, format) {
var args = [];
for (var _i = 2; _i < arguments.length; _i++) {
args[_i - 2] = arguments[_i];
}
/* istanbul ignore next */
if (!condition) {
var argIndex_1 = 0;
var error = new Error(format.replace(/%s/g, function () { return makePrintable(args[argIndex_1++]); }));
error.framesToPop = 1; // we don't care about verify's own frame
throw error;
}
}
Updraft.verify = verify;
})(Updraft || (/* istanbul ignore next */ Updraft = {}));
///<reference path="./Text.ts"/>
///<reference path="./verify.ts"/>
var Updraft;
(function (Updraft) {
(function (ColumnType) {
ColumnType[ColumnType["int"] = 0] = "int";
ColumnType[ColumnType["real"] = 1] = "real";
ColumnType[ColumnType["bool"] = 2] = "bool";
ColumnType[ColumnType["text"] = 3] = "text";
ColumnType[ColumnType["enum"] = 4] = "enum";
ColumnType[ColumnType["date"] = 5] = "date";
ColumnType[ColumnType["datetime"] = 6] = "datetime";
ColumnType[ColumnType["json"] = 7] = "json";
ColumnType[ColumnType["set"] = 8] = "set";
})(Updraft.ColumnType || (Updraft.ColumnType = {}));
var ColumnType = Updraft.ColumnType;
/**
* Column in db. Use static methods to create columns.
*/
var Column = (function () {
function Column(type) {
this.type = type;
if (type == ColumnType.bool) {
this.defaultValue = false;
}
}
/**
* Column is the primary key. Only one column can have this set.
*/
Column.prototype.Key = function () {
this.isKey = true;
return this;
};
/**
* Create an index for this column for faster queries.
*/
Column.prototype.Index = function () {
this.isIndex = true;
return this;
};
/**
* Set a default value for the column
*/
// TODO
Column.prototype.Default = function (value) {
if (this.type == ColumnType.bool) {
value = value ? true : false;
}
this.defaultValue = value;
return this;
};
Column.prototype.deserialize = function (value) {
switch (this.type) {
case ColumnType.int:
case ColumnType.real:
case ColumnType.text:
return value;
case ColumnType.bool:
return value ? true : false;
case ColumnType.json:
return Updraft.fromText(value);
case ColumnType.enum:
if (typeof this.enum.get === "function") {
var enumValue = this.enum.get(value);
Updraft.verify(!value || enumValue, "error getting enum value %s", value);
return enumValue;
}
Updraft.verify(value in this.enum, "enum value %s not in %s", value, this.enum);
return this.enum[value];
case ColumnType.date:
case ColumnType.datetime:
Updraft.verify(!value || parseFloat(value) == value, "expected date to be stored as a number: %s", value);
return value ? new Date(parseFloat(value) * 1000) : undefined;
case ColumnType.set:
Updraft.verify(value instanceof Set, "value should already be a set");
return value;
/* istanbul ignore next */
default:
throw new Error("unsupported column type " + ColumnType[this.type]);
}
};
Column.prototype.serialize = function (value) {
switch (this.type) {
case ColumnType.int:
case ColumnType.real:
case ColumnType.text:
return value;
case ColumnType.bool:
return value ? 1 : 0;
case ColumnType.json:
return Updraft.toText(value);
case ColumnType.enum:
/* istanbul ignore if: safe to store these in db, though it's probably an error to be anything other than a number/object */
if (typeof value === "string" || typeof value === undefined || value === null) {
return value;
}
else if (typeof value === "number") {
Updraft.verify(value in this.enum, "enum doesn't contain %s", value);
return this.enum[value];
}
Updraft.verify(typeof value.toString === "function", "expected an enum value supporting toString(); got %s", value);
return value.toString();
case ColumnType.date:
case ColumnType.datetime:
Updraft.verify(value == undefined || value instanceof Date, "expected a date, got %s", value);
var date = (value == undefined) ? null : (value.getTime() / 1000);
return date;
/* istanbul ignore next */
default:
throw new Error("unsupported column type " + ColumnType[this.type]);
}
};
/** create a column with "INTEGER" affinity */
Column.Int = function () {
return new Column(ColumnType.int);
};
/** create a column with "REAL" affinity */
Column.Real = function () {
return new Column(ColumnType.real);
};
/** create a column with "BOOL" affinity */
Column.Bool = function () {
return new Column(ColumnType.bool);
};
/** create a column with "TEXT" affinity */
Column.Text = function () {
return new Column(ColumnType.text);
};
/** create a column with "TEXT" affinity */
Column.String = function () {
return new Column(ColumnType.text);
};
/** a typescript enum or javascript object with instance method "toString" and class method "get" (e.g. {@link https://github.com/adrai/enum}). */
Column.Enum = function (enum_) {
var c = new Column(ColumnType.enum);
c.enum = enum_;
return c;
};
/** a javascript Date objct, stored in db as seconds since Unix epoch (time_t) [note: precision is seconds] */
Column.Date = function () {
return new Column(ColumnType.date);
};
/** a javascript Date objct, stored in db as seconds since Unix epoch (time_t) [note: precision is seconds] */
Column.DateTime = function () {
return new Column(ColumnType.datetime);
};
/** object will be serialized & restored as JSON text */
Column.JSON = function () {
return new Column(ColumnType.json);
};
/** unordered collection */
Column.Set = function (type) {
var c = new Column(ColumnType.set);
c.element = new Column(type);
return c;
};
Column.sql = function (val) {
var stmt = "";
switch (val.type) {
case ColumnType.int:
stmt = "INTEGER";
break;
case ColumnType.bool:
stmt = "BOOLEAN NOT NULL";
break;
case ColumnType.real:
stmt = "REAL";
break;
case ColumnType.text:
stmt = "TEXT";
break;
case ColumnType.json:
stmt = "CLOB";
break;
case ColumnType.enum:
stmt = "CHARACTER(20)";
break;
case ColumnType.date:
stmt = "DATE";
break;
case ColumnType.datetime:
stmt = "DATETIME";
break;
/* istanbul ignore next */
default:
throw new Error("unsupported type " + ColumnType[val.type]);
}
if ("defaultValue" in val) {
var escape = function (x) {
/* istanbul ignore else */
if (typeof x === "number") {
return x;
}
else if (typeof x === "string") {
return "'" + x.replace(/'/g, "''") + "'";
}
else {
Updraft.verify(false, "default value (%s) must be number or string", x);
}
};
stmt += " DEFAULT " + escape(val.serialize(val.defaultValue));
}
return stmt;
};
Column.fromSql = function (text) {
var parts = text.split(" ");
var col = null;
switch (parts[0]) {
case "INTEGER":
col = Column.Int();
break;
case "BOOLEAN":
col = Column.Bool();
break;
case "REAL":
col = Column.Real();
break;
case "TEXT":
col = Column.Text();
break;
case "CLOB":
col = Column.JSON();
break;
case "CHARACTER(20)":
col = new Column(ColumnType.enum);
break;
case "DATE":
col = Column.Date();
break;
case "DATETIME":
col = Column.DateTime();
break;
/* istanbul ignore next */
default:
throw new Error("unsupported type: " + ColumnType[parts[0]]);
}
var match = text.match(/DEFAULT\s+'((?:[^']|'')*)'/i);
if (match) {
var val = match[1].replace(/''/g, "'");
col.Default(val);
}
else {
match = text.match(/DEFAULT\s+(\S+)/i);
if (match) {
var val = match[1];
var valnum = parseFloat(val);
/* istanbul ignore else: unlikely to be anything but a number */
if (val == valnum) {
val = valnum;
}
col.Default(val);
}
}
return col;
};
Column.equal = function (a, b) {
if (a.type != b.type) {
return false;
}
if ((a.defaultValue || b.defaultValue) && (a.defaultValue != b.defaultValue)) {
return false;
}
/* istanbul ignore next: I don't think this is possible */
if ((a.isKey || b.isKey) && (a.isKey != b.isKey)) {
return false;
}
return true;
};
return Column;
}());
Updraft.Column = Column;
})(Updraft || (/* istanbul ignore next */ Updraft = {}));
var Updraft;
(function (Updraft) {
function DbExecuteSequence(transaction, statements, nextCallback) {
var i = 0;
var act = function (tx) {
if (i < statements.length) {
var which = statements[i];
i++;
tx.executeSql(which.sql, which.params, act);
}
else {
nextCallback(tx);
}
};
act(transaction);
}
Updraft.DbExecuteSequence = DbExecuteSequence;
})(Updraft || (/* istanbul ignore next */ Updraft = {}));
// written to React"s immutability helpers spec
// see https://facebook.github.io/react/docs/update.html
///<reference path="../typings/index.d.ts"/>
///<reference path="./assign.ts"/>
///<reference path="./verify.ts"/>
var Updraft;
(function (Updraft) {
function shallowCopy(x) {
/* istanbul ignore else: not sure about this one */
if (Array.isArray(x)) {
return x.concat();
}
else if (x instanceof Set) {
return new Set(x);
}
else if (typeof x === "object") {
return Updraft.assign(new x.constructor(), x);
}
else {
/* istanbul ignore next: correct AFAIK but unreachable */
return x;
}
}
Updraft.shallowCopy = shallowCopy;
function shallowEqual(a, b) {
if (Array.isArray(a) && Array.isArray(b)) {
var aa = a;
var bb = b;
if (aa.length == bb.length) {
for (var i = 0; i < aa.length; i++) {
if (aa[i] != bb[i]) {
return false;
}
}
return true;
}
return false;
}
else if (a instanceof Set && b instanceof Set) {
var aa = a;
var bb_1 = b;
if (aa.size == bb_1.size) {
var equal_1 = true;
aa.forEach(function (elt) {
if (equal_1 && !bb_1.has(elt)) {
equal_1 = false;
}
});
return equal_1;
}
return false;
}
else if (a instanceof Date && b instanceof Date) {
return a.getTime() == b.getTime();
}
else if (a && typeof a == "object" && b && typeof b == "object") {
var akeys = Object.keys(a);
var bkeys = Object.keys(b);
if (akeys.length == bkeys.length) {
for (var _i = 0, akeys_1 = akeys; _i < akeys_1.length; _i++) {
var key = akeys_1[_i];
if (!(key in b) || a[key] != b[key]) {
return false;
}
}
return true;
}
return false;
}
return a == b;
}
Updraft.shallowEqual = shallowEqual;
Updraft.hasOwnProperty = {}.hasOwnProperty;
function keyOf(obj) { return Object.keys(obj)[0]; }
Updraft.keyOf = keyOf;
var command = {
set: keyOf({ $set: null }),
increment: keyOf({ $inc: null }),
push: keyOf({ $push: null }),
unshift: keyOf({ $unshift: null }),
splice: keyOf({ $splice: null }),
merge: keyOf({ $merge: null }),
add: keyOf({ $add: null }),
deleter: keyOf({ $delete: null }),
};
function verifyArrayCase(value, spec, c) {
Updraft.verify(Array.isArray(value), "update(): expected target of %s to be an array; got %s.", c, value);
var specValue = spec[c];
Updraft.verify(Array.isArray(specValue), "update(): expected spec of %s to be an array; got %s. " +
"Did you forget to wrap your parameter in an array?", c, specValue);
}
function verifySetCase(value, spec, c) {
Updraft.verify(value instanceof Set, "update(): expected target of %s to be a set; got %s.", c, value);
var specValue = spec[c];
Updraft.verify(Array.isArray(specValue), "update(): expected spec of %s to be an array; got %s. " +
"Did you forget to wrap your parameter in an array?", c, specValue);
}
function update(value, spec) {
Updraft.verify(typeof spec === "object", "update(): You provided a key path to update() that did not contain one " +
"of %s. Did you forget to include {%s: ...}?", Object.keys(command).join(", "), command.set);
// verify(
// Object.keys(spec).reduce( function(previousValue: boolean, currentValue: string): boolean {
// return previousValue && (keyOf(spec[currentValue]) in command);
// }, true),
// "update(): argument has an unknown key; supported keys are (%s). delta: %s",
// Object.keys(command).join(", "),
// spec
// );
if (Updraft.hasOwnProperty.call(spec, command.set)) {
Updraft.verify(Object.keys(spec).length === 1, "Cannot have more than one key in an object with %s", command.set);
return shallowEqual(value, spec[command.set]) ? value : spec[command.set];
}
if (Updraft.hasOwnProperty.call(spec, command.increment)) {
Updraft.verify(typeof (value) === "number" && typeof (spec[command.increment]) === "number", "Source (%s) and argument (%s) to %s must be numbers", value, spec[command.increment], command.increment);
return value + spec[command.increment];
}
var changed = false;
if (Updraft.hasOwnProperty.call(spec, command.merge)) {
var mergeObj = spec[command.merge];
var nextValue_1 = shallowCopy(value);
Updraft.verify(mergeObj && typeof mergeObj === "object", "update(): %s expects a spec of type 'object'; got %s", command.merge, mergeObj);
Updraft.verify(nextValue_1 && typeof nextValue_1 === "object", "update(): %s expects a target of type 'object'; got %s", command.merge, nextValue_1);
Updraft.assign(nextValue_1, spec[command.merge]);
return shallowEqual(value, nextValue_1) ? value : nextValue_1;
}
if (Updraft.hasOwnProperty.call(spec, command.deleter) && (typeof value === "object") && !(value instanceof Set)) {
var keys = spec[command.deleter];
Updraft.verify(keys && Array.isArray(keys), "update(): %s expects a spec of type 'array'; got %s", command.deleter, keys);
var nextValue_2 = shallowCopy(value);
changed = false;
keys.forEach(function (key) {
if (key in value) {
delete nextValue_2[key];
changed = true;
}
});
return changed ? nextValue_2 : value;
}
if (Updraft.hasOwnProperty.call(spec, command.push)) {
var nextValue_3 = shallowCopy(value) || [];
verifyArrayCase(nextValue_3, spec, command.push);
if (spec[command.push].length) {
nextValue_3.push.apply(nextValue_3, spec[command.push]);
return nextValue_3;
}
else {
return value;
}
}
if (Updraft.hasOwnProperty.call(spec, command.unshift)) {
verifyArrayCase(value, spec, command.unshift);
if (spec[command.unshift].length) {
var nextValue_4 = shallowCopy(value);
nextValue_4.unshift.apply(nextValue_4, spec[command.unshift]);
return nextValue_4;
}
else {
return value;
}
}
if (Updraft.hasOwnProperty.call(spec, command.splice)) {
var nextValue_5 = shallowCopy(value);
Updraft.verify(Array.isArray(value), "Expected %s target to be an array; got %s", command.splice, value);
Updraft.verify(Array.isArray(spec[command.splice]), "update(): expected spec of %s to be an array of arrays; got %s. " +
"Did you forget to wrap your parameters in an array?", command.splice, spec[command.splice]);
spec[command.splice].forEach(function (args) {
Updraft.verify(Array.isArray(args), "update(): expected spec of %s to be an array of arrays; got %s. " +
"Did you forget to wrap your parameters in an array?", command.splice, spec[command.splice]);
nextValue_5.splice.apply(nextValue_5, args);
});
return shallowEqual(nextValue_5, value) ? value : nextValue_5;
}
if (Updraft.hasOwnProperty.call(spec, command.add)) {
var nextValue_6 = shallowCopy(value) || new Set();
verifySetCase(nextValue_6, spec, command.add);
spec[command.add].forEach(function (item) {
if (!nextValue_6.has(item)) {
nextValue_6.add(item);
changed = true;
}
});
return changed ? nextValue_6 : value;
}
if (Updraft.hasOwnProperty.call(spec, command.deleter) && (value instanceof Set)) {
var nextValue_7 = shallowCopy(value);
verifySetCase(value, spec, command.deleter);
spec[command.deleter].forEach(function (item) {
if (nextValue_7.delete(item)) {
changed = true;
}
});
return changed ? nextValue_7 : value;
}
var nextValue;
for (var k in spec) {
if (typeof value === "object" && !(command.hasOwnProperty(k))) {
var oldValue = value[k];
var newValue = update(oldValue, spec[k]);
if (oldValue !== newValue) {
if (!nextValue) {
nextValue = shallowCopy(value);
}
nextValue[k] = newValue;
changed = true;
}
}
}
return changed ? nextValue : value;
}
Updraft.update = update;
})(Updraft || (/* istanbul ignore next */ Updraft = {}));
///<reference path="./Column.ts"/>
///<reference path="./verify.ts"/>
var Updraft;
(function (Updraft) {
(function (OrderBy) {
OrderBy[OrderBy["ASC"] = 0] = "ASC";
OrderBy[OrderBy["DESC"] = 1] = "DESC";
})(Updraft.OrderBy || (Updraft.OrderBy = {}));
var OrderBy = Updraft.OrderBy;
var Table = (function () {
function Table(spec) {
this.spec = spec;
this.key = tableKey(spec);
}
Table.prototype.keyValue = function (element) {
Updraft.verify(this.key in element, "object does not have key field '%s' set: %s", this.key, element);
return element[this.key];
};
return Table;
}());
Updraft.Table = Table;
function tableKey(spec) {
var key = null;
for (var name_1 in spec.columns) {
var column = spec.columns[name_1];
Updraft.verify(column, "column '%s' is not in %s", name_1, spec);
if (column.isKey) {
Updraft.verify(!key, "Table %s has more than one key- %s and %s", spec.name, key, name_1);
key = name_1;
}
}
Updraft.verify(key, "Table %s does not have a key", spec.name);
return key;
}
Updraft.tableKey = tableKey;
})(Updraft || (/* istanbul ignore next */ Updraft = {}));
///<reference path="./Column.ts"/>
///<reference path="./Delta.ts"/>
///<reference path="./Database.ts"/>
///<reference path="./Table.ts"/>
///<reference path="./Text.ts"/>
///<reference path="./assign.ts"/>
///<reference path="./verify.ts"/>
var Updraft;
(function (Updraft) {
function startsWith(str, val) {
return str.lastIndexOf(val, 0) === 0;
}
function quote(str) {
return '"' + str + '"';
}
var MAX_VARIABLES = 999;
var ROWID = "rowid";
var COUNT = "COUNT(*)";
var DEFAULT_SYNCID = 100;
var internal_prefix = "updraft_";
var internal_column_deleted = internal_prefix + "deleted";
var internal_column_time = internal_prefix + "time";
var internal_column_latest = internal_prefix + "latest";
var internal_column_composed = internal_prefix + "composed";
var internal_column_source = internal_prefix + "source";
var internal_column_syncId = internal_prefix + "syncId";
var internalColumn = {};
internalColumn[internal_column_deleted] = Updraft.Column.Bool();
internalColumn[internal_column_time] = Updraft.Column.Int().Key();
internalColumn[internal_column_latest] = Updraft.Column.Bool();
internalColumn[internal_column_composed] = Updraft.Column.Bool();
internalColumn[internal_column_source] = Updraft.Column.String().Index();
internalColumn[internal_column_syncId] = Updraft.Column.Int().Default(DEFAULT_SYNCID).Index();
var localKey_guid = "guid";
var localKey_syncId = "syncId";
var deleteRow_action = (_a = {}, _a[internal_column_deleted] = { $set: true }, _a);
var keyValueTableSpec = {
name: internal_prefix + "keyValues",
columns: {
key: Updraft.Column.String().Key(),
value: Updraft.Column.JSON(),
}
};
var localsTableSpec = {
name: internal_prefix + "locals",
columns: {
key: Updraft.Column.String().Key(),
value: Updraft.Column.JSON(),
}
};
var Store = (function () {
function Store(params) {
this.params = params;
this.tables = [];
this.db = null;
Updraft.verify(this.params.db, "must pass a DbWrapper");
this.localsTable = this.createUntrackedTable(localsTableSpec);
this.keyValueTable = this.createTrackedTable(keyValueTableSpec, true);
}
Store.prototype.createTable = function (tableSpec) {
return this.createTrackedTable(tableSpec, false);
};
Store.prototype.createTrackedTable = function (tableSpec, internal) {
Updraft.verify(!this.db, "createTable() can only be added before open()");
if (!internal) {
Updraft.verify(!startsWith(tableSpec.name, internal_prefix), "table name %s cannot begin with %s", tableSpec.name, internal_prefix);
}
for (var col in tableSpec.columns) {
Updraft.verify(!startsWith(col, internal_prefix), "table %s column %s cannot begin with %s", tableSpec.name, col, internal_prefix);
}
var table = this.createTableObject(tableSpec);
(_a = this.tables).push.apply(_a, createInternalTableSpecs(table));
this.tables.push(createChangeTableSpec(table));
return table;
/* istanbul ignore next */ var _a;
};
Store.prototype.createUntrackedTable = function (tableSpec) {
buildIndices(tableSpec);
var table = this.createTableObject(tableSpec);
this.tables.push(tableSpec);
return table;
};
Store.prototype.createTableObject = function (tableSpec) {
var _this = this;
var table = new Updraft.Table(tableSpec);
table.add = function () {
var changes = [];
for (var _i = 0; _i < arguments.length; _i++) {
changes[_i - 0] = arguments[_i];
}
changes.forEach(function (change) { return change.table = table; });
return _this.add.apply(_this, changes);
};
table.find = function (queryArg, opts) {
return _this.find(table, queryArg, opts);
};
return table;
};
Store.prototype.open = function () {
var _this = this;
Updraft.verify(!this.db, "open() called more than once!");
Updraft.verify(this.tables.length, "open() called before any tables were added");
this.db = this.params.db;
return Promise.resolve()
.then(function () { return _this.readSchema(); })
.then(function (schema) {
return new Promise(function (resolve, reject) {
var i = 0;
var act = function (transaction) {
if (i < _this.tables.length) {
var table = _this.tables[i];
i++;
_this.syncTable(transaction, schema, table, act);
}
else {
_this.loadLocals(transaction, function () {
_this.loadKeyValues(transaction, function () {
transaction.commit(resolve);
});
});
}
};
_this.db.transaction(act, reject);
});
});
};
Store.prototype.readSchema = function () {
var _this = this;
Updraft.verify(this.db, "readSchema(): not opened");
return new Promise(function (resolve, reject) {
_this.db.readTransaction(function (transaction) {
return transaction.executeSql("SELECT name, tbl_name, type, sql FROM sqlite_master", [], function (tx, resultSet) {
var schema = {};
for (var i = 0; i < resultSet.length; i++) {
var row = resultSet[i];
if (row.name[0] != "_" && !startsWith(row.name, "sqlite")) {
switch (row.type) {
case "table":
schema[row.name] = tableFromSql(row.name, row.sql);
break;
case "index":
var index = indexFromSql(row.sql);
if (index.length == 1) {
var col = index[0];
Updraft.verify(row.tbl_name in schema, "table %s used by index %s should have been returned first", row.tbl_name, row.name);
Updraft.verify(col in schema[row.tbl_name].columns, "table %s does not have column %s used by index %s", row.tbl_name, col, row.name);
schema[row.tbl_name].columns[col].isIndex = true;
}
else {
schema[row.tbl_name].indices.push(index);
}
break;
}
}
}
transaction.commit(function () { return resolve(schema); });
});
}, reject);
});
};
Store.prototype.syncTable = function (transaction, schema, spec, nextCallback) {
if (spec.name in schema) {
var oldColumns_1 = schema[spec.name].columns;
var newColumns_1 = spec.columns;
var recreateTable = false;
for (var colName in oldColumns_1) {
if (!(colName in newColumns_1)) {
recreateTable = true;
break;
}
var oldCol = oldColumns_1[colName];
var newCol = newColumns_1[colName];
if (!Updraft.Column.equal(oldCol, newCol)) {
recreateTable = true;
break;
}
}
var renamedColumns_1 = Updraft.shallowCopy(spec.renamedColumns) || {};
for (var colName in renamedColumns_1) {
if (colName in oldColumns_1) {
recreateTable = true;
}
else {
delete renamedColumns_1[colName];
}
}
var addedColumns = {};
if (!recreateTable) {
for (var _i = 0, _a = selectableColumns(spec, newColumns_1); _i < _a.length; _i++) {
var colName = _a[_i];
if (!(colName in oldColumns_1)) {
addedColumns[colName] = newColumns_1[colName];
}
}
}
if (recreateTable) {
// recreate and migrate data
var tempTableName_1 = "temp_" + spec.name;
var changeTableName_1 = getChangeTableName(spec.name);
dropTable(transaction, tempTableName_1, function (tx2) {
createTable(tx2, tempTableName_1, spec.columns, function (tx3) {
copyData(tx3, spec.name, tempTableName_1, oldColumns_1, newColumns_1, renamedColumns_1, function (tx4) {
dropTable(tx4, spec.name, function (tx5) {
renameTable(tx5, tempTableName_1, spec.name, function (tx6) {
migrateChangeTable(tx6, changeTableName_1, oldColumns_1, newColumns_1, renamedColumns_1, function (tx7) {
createIndices(tx7, schema, spec, true, nextCallback);
});
});
});
});
});
});
}
else if (!isEmpty(addedColumns)) {
// alter table, add columns
var stmts_1 = [];
Object.keys(addedColumns).forEach(function (colName) {
var col = spec.columns[colName];
var columnDecl = quote(colName) + " " + Updraft.Column.sql(col);
stmts_1.push({ sql: "ALTER TABLE " + spec.name + " ADD COLUMN " + columnDecl });
});
Updraft.DbExecuteSequence(transaction, stmts_1, function (tx2) {
createIndices(tx2, schema, spec, false, nextCallback);
});
}
else {
// no table modification is required
createIndices(transaction, schema, spec, false, nextCallback);
}
}
else {
// create new table
createTable(transaction, spec.name, spec.columns, function (tx2) {
createIndices(tx2, schema, spec, true, nextCallback);
});
}
};
Store.prototype.loadLocals = function (transaction, nextCallback) {
var _this = this;
transaction.executeSql("SELECT key, value FROM " + this.localsTable.spec.name, [], function (tx2, rows) {
rows.forEach(function (row) {
switch (row.key) {
case localKey_guid:
_this.guid = row.value;
break;
case localKey_syncId:
_this.syncId = row.value;
break;
/* istanbul ignore next */
default:
Updraft.verify(false, "unknown key %s in %s", row.key, _this.localsTable.spec.name);
}
});
var initGuid = function (tx, next) {
if (!_this.guid && _this.params.generateGuid) {
_this.guid = _this.params.generateGuid();
_this.saveLocal(tx, localKey_guid, _this.guid, next);
}
else {
next(tx);
}
};
var initSyncId = function (tx, next) {
if (!_this.syncId) {
_this.syncId = DEFAULT_SYNCID;
_this.saveLocal(tx, localKey_syncId, _this.syncId, next);
}
else {
next(tx);
}
};
initGuid(tx2, function (tx3) {
initSyncId(tx3, nextCallback);
});
});
};
Store.prototype.saveLocal = function (transaction, key, value, nextCallback) {
var sql = "INSERT INTO " + this.localsTable.spec.name + " (key, value) VALUES (?, ?)";
transaction.executeSql(sql, [key, value], nextCallback);
};
Store.prototype.loadKeyValues = function (transaction, nextCallback) {
var _this = this;
return runQuery(transaction, this.keyValueTable, {}, undefined, undefined, function (tx2, rows) {
_this.keyValues = {};
rows.forEach(function (row) {
_this.keyValues[row.key] = row.value;
});
nextCallback(tx2);
});
};
Store.prototype.getValue = function (key) {
return this.keyValues[key];
};
Store.prototype.setValue = function (key, value) {
this.keyValues[key] = value;
return this.keyValueTable.add({ create: { key: key, value: value } });
};
Store.prototype.add = function () {
var changes = [];
for (var _i = 0; _i < arguments.length; _i++) {
changes[_i - 0] = arguments[_i];
}
return this.addFromSource(changes, null);
};
Store.prototype.addFromSource = function (changes, source) {
var _this = this;
Updraft.verify(this.db, "addFromSource(): not opened");
return new Promise(function (promiseResolve, reject) {
var syncId = _this.syncId;
Updraft.verify(syncId, "invalid syncId");
var tableKeySet = [];
changes.forEach(function (change) {
if (change.create) {
var key = change.table.keyValue(change.create);
var keys = null;
var duplicateKeys = null;
var allKeys = null;
for (var j = 0; j < tableKeySet.length; j++) {
/* istanbul ignore else */
if (tableKeySet[j].table === change.table) {
duplicateKeys = tableKeySet[j].duplicateKeys;
allKeys = tableKeySet[j].allKeys;
for (var k = 0; k < tableKeySet[j].keysArray.length; k++) {
var kk = tableKeySet[j].keysArray[k];
if (kk.size < MAX_VARIABLES) {
keys = kk;
break;
}
}
if (!keys) {
keys = new Set();
tableKeySet[j].keysArray.push(keys);
}
break;
}
}
if (keys == null) {
keys = new Set();
duplicateKeys = new Set();
allKeys = new Set();
tableKeySet.push({ table: change.table, keysArray: [keys], allKeys: allKeys, duplicateKeys: duplicateKeys, existingKeys: new Set() });
}
if (allKeys.has(key)) {
duplicateKeys.add(key);
}
allKeys.add(key);
keys.add(key);
}
});
var findIdx = 0;
var findBatchIdx = 0;
var changeIdx = 0;
var toResolve = new Set();
var findExistingIds = null;
var insertNextChange = null;
var resolveChanges = null;
findExistingIds = function (transaction) {
if (findIdx < tableKeySet.length) {
var table_1 = tableKeySet[findIdx].table;
var keysArray_1 = tableKeySet[findIdx].keysArray;
var duplicateKeys_1 = tableKeySet[findIdx].duplicateKeys;
var existingKeys_1 = tableKeySet[findIdx].existingKeys;
var notDuplicatedValues_1 = [];
keysArray_1[findBatchIdx].forEach(function (key) {
if (!duplicateKeys_1.has(key)) {
notDuplicatedValues_1.push(key);
}
});
var query = (_a = {}, _a[table_1.key] = { $in: notDuplicatedValues_1 }, _a);
var opts = { fields: (_b = {}, _b[table_1.key] = true, _b) };
runQuery(transaction, table_1, query, opts, null, function (tx, rows) {
for (var _i = 0, rows_1 = rows; _i < rows_1.length; _i++) {
var row = rows_1[_i];
existingKeys_1.add(row[table_1.key]);
}
findBatchIdx++;
if (findBatchIdx >= keysArray_1.length) {
findIdx++;
findBatchIdx = 0;
}
findExistingIds(transaction);
});
}
else {
insertNextChange(transaction);
}
/* istanbul ignore next */ var _a, _b;
};
insertNextChange = function (transaction) {
if (changeIdx < changes.length) {
var change = changes[changeIdx];
changeIdx++;
var table_2 = change.table;
Updraft.verify(table_2, "change must specify table");
var changeTable = getChangeTableName(table_2.spec.name);
var time = change.time || Date.now();
Updraft.verify((change.create ? 1 : 0) + (change.update ? 1 : 0) + (change.delete ? 1 : 0) === 1, "change (%s) must specify exactly one action at a time", change);
var existingKeys_2 = null;
tableKeySet.some(function (tk) {
/* istanbul ignore else */
if (tk.table === table_2) {
existingKeys_2 = tk.existingKeys;
return true;
}
else {
return false;
}
});
if (change.create) {
// append internal column values
var element = Updraft.assign({}, change.create, (_a = {}, _a[internal_column_time] = time, _a), (_b = {}, _b[internal_column_source] = source, _b), (_c = {}, _c[internal_column_syncId] = syncId, _c));
var key = table_2.keyValue(element);
// optimization: don't resolve elements that aren't already in the db- just mark them as latest
if (existingKeys_2.has(key)) {
toResolve.add({ table: table_2, key: key });
}
else {
element[internal_column_latest] = true;
}
insertElement(transaction, table_2, element, insertNextChange);
}
if (change.update || change.delete) {
var changeRow_1 = {
key: null,
time: time,
change: null,
source: source,
syncId: syncId
};
if (change.update) {
// store deltas
var delta = Updraft.shallowCopy(change.update);
changeRow_1.key = table_2.keyValue(delta);
delete delta[table_2.key];
changeRow_1.change = serializeDelta(delta, table_2.spec);
}
else {
// mark deleted
changeRow_1.key = change.delete;
changeRow_1.change = serializeDelta(deleteRow_action, table_2.spec);
}
// insert into delta table
var columns = Object.keys(changeRow_1);
var values = columns.map(function (k) { return changeRow_1[k]; });
toResolve.add({ table: table_2, key: changeRow_1.key });
insert(transaction, changeTable, columns, values, insertNextChange);
}
/* istanbul ignore next */
if (!change.create && !change.update && !change.delete) {
throw new Error("no operation specified for delta- should be one of create, update, or delete");
}
}
else {
resolveChanges(transaction);
}
/* istanbul ignore next */ var _a, _b, _c;
};
resolveChanges = function (transaction) {
var j = 0;
var toResolveArray = [];
toResolve.forEach(function (keyValue) { return toResolveArray.push(keyValue); });
var resolveNextChange = function (tx2) {
if (j < toResolveArray.length) {
var keyValue = toResolveArray[j];
j++;
resolve(tx2, keyValue.table, keyValue.key, resolveNextChange);
}
else {
tx2.commit(promiseResolve);
}
};
resolveNextChange(transaction);
};
_this.db.transaction(findExistingIds, reject);
});
};
Store.prototype.find = function (table, queryArg, opts) {
var _this = this;
return new Promise(function (resolve, reject) {
_this.db.readTransaction(function (transaction) {
var queries = Array.isArray(queryArg) ? queryArg : [queryArg];
var qs = queries.map(function (query) {
return Updraft.assign({}, query, (_a = {},
_a[internal_column_deleted] = false,
_a[internal_column_latest] = true,
_a
));
/* istanbul ignore next */ var _a;
});
runQuery(tran