UNPKG

updraft

Version:

Javascript ORM-like storage in SQLite (WebSQL or other), synced to the cloud

1,116 lines 90.9 kB
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