backbone-indexeddb.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. (function (root, factory) {
  2. if (typeof define === 'function' && define.amd) {
  3. // AMD. Register as an anonymous module.
  4. define(['backbone', 'underscore'], factory);
  5. } else if (typeof exports === 'object') {
  6. // Node. Does not work with strict CommonJS, but
  7. // only CommonJS-like environments that support module.exports,
  8. // like Node.
  9. module.exports = factory(require('backbone'), require('underscore'));
  10. } else {
  11. // Browser globals (root is window)
  12. root.returnExports = factory(root.Backbone, root._);
  13. }
  14. }(this, function (Backbone, _) {
  15. // Generate four random hex digits.
  16. function S4() {
  17. return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
  18. }
  19. // Generate a pseudo-GUID by concatenating random hexadecimal.
  20. function guid() {
  21. return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
  22. }
  23. if ( _(indexedDB).isUndefined() ) { return; }
  24. // Driver object
  25. // That's the interesting part.
  26. // There is a driver for each schema provided. The schema is a te combination of name (for the database), a version as well as migrations to reach that
  27. // version of the database.
  28. function Driver(schema, ready, nolog, onerror) {
  29. this.schema = schema;
  30. this.ready = ready;
  31. this.error = null;
  32. this.transactions = []; // Used to list all transactions and keep track of active ones.
  33. this.db = null;
  34. this.nolog = nolog;
  35. this.onerror = onerror;
  36. var lastMigrationPathVersion = _.last(this.schema.migrations).version;
  37. if (!this.nolog) debugLog("opening database " + this.schema.id + " in version #" + lastMigrationPathVersion);
  38. this.dbRequest = indexedDB.open(this.schema.id,lastMigrationPathVersion); //schema version need to be an unsigned long
  39. this.launchMigrationPath = function(dbVersion) {
  40. var transaction = this.dbRequest.transaction;
  41. var clonedMigrations = _.clone(schema.migrations);
  42. this.migrate(transaction, clonedMigrations, dbVersion, {
  43. error: function (event) {
  44. this.error = "Database not up to date. " + dbVersion + " expected was " + lastMigrationPathVersion;
  45. }.bind(this)
  46. });
  47. };
  48. this.dbRequest.onblocked = function(event){
  49. if (!this.nolog) debugLog("connection to database blocked");
  50. }
  51. this.dbRequest.onsuccess = function (e) {
  52. this.db = e.target.result; // Attach the connection ot the queue.
  53. var currentIntDBVersion = (parseInt(this.db.version) || 0); // we need convert beacuse chrome store in integer and ie10 DP4+ in int;
  54. var lastMigrationInt = (parseInt(lastMigrationPathVersion) || 0); // And make sure we compare numbers with numbers.
  55. if (currentIntDBVersion === lastMigrationInt) { //if support new event onupgradeneeded will trigger the ready function
  56. // No migration to perform!
  57. this.ready();
  58. } else if (currentIntDBVersion < lastMigrationInt ) {
  59. // We need to migrate up to the current migration defined in the database
  60. this.launchMigrationPath(currentIntDBVersion);
  61. } else {
  62. // Looks like the IndexedDB is at a higher version than the current driver schema.
  63. this.error = "Database version is greater than current code " + currentIntDBVersion + " expected was " + lastMigrationInt;
  64. }
  65. }.bind(this);
  66. this.dbRequest.onerror = function (e) {
  67. // Failed to open the database
  68. this.error = "Couldn't not connect to the database"
  69. if (!this.nolog) debugLog("Couldn't not connect to the database");
  70. this.onerror();
  71. }.bind(this);
  72. this.dbRequest.onabort = function (e) {
  73. // Failed to open the database
  74. this.error = "Connection to the database aborted"
  75. if (!this.nolog) debugLog("Connection to the database aborted");
  76. this.onerror();
  77. }.bind(this);
  78. this.dbRequest.onupgradeneeded = function(iDBVersionChangeEvent){
  79. this.db =iDBVersionChangeEvent.target.result;
  80. var newVersion = iDBVersionChangeEvent.newVersion;
  81. var oldVersion = iDBVersionChangeEvent.oldVersion;
  82. // Fix Safari 8 and iOS 8 bug
  83. // at the first connection oldVersion is equal to 9223372036854776000
  84. // but the real value is 0
  85. if (oldVersion > 99999999999)
  86. oldVersion = 0;
  87. if (!this.nolog) debugLog("onupgradeneeded = " + oldVersion + " => " + newVersion);
  88. this.launchMigrationPath(oldVersion);
  89. }.bind(this);
  90. }
  91. function debugLog(str) {
  92. if (typeof window !== "undefined" && typeof window.console !== "undefined" && typeof window.console.log !== "undefined") {
  93. window.console.log(str);
  94. }
  95. else if(console.log !== "undefined") {
  96. console.log(str)
  97. }
  98. }
  99. // Driver Prototype
  100. Driver.prototype = {
  101. // Tracks transactions. Mostly for debugging purposes. TO-IMPROVE
  102. _track_transaction: function(transaction) {
  103. this.transactions.push(transaction);
  104. function removeIt() {
  105. var idx = this.transactions.indexOf(transaction);
  106. if (idx !== -1) {this.transactions.splice(idx); }
  107. };
  108. transaction.oncomplete = removeIt.bind(this);
  109. transaction.onabort = removeIt.bind(this);
  110. transaction.onerror = removeIt.bind(this);
  111. },
  112. // Performs all the migrations to reach the right version of the database.
  113. migrate: function (transaction, migrations, version, options) {
  114. transaction.onerror = options.error;
  115. transaction.onabort = options.error;
  116. if (!this.nolog) debugLog("migrate begin version from #" + version);
  117. var that = this;
  118. var migration = migrations.shift();
  119. if (migration) {
  120. if (!version || version < migration.version) {
  121. // We need to apply this migration-
  122. if (typeof migration.before == "undefined") {
  123. migration.before = function (next) {
  124. next();
  125. };
  126. }
  127. if (typeof migration.after == "undefined") {
  128. migration.after = function (next) {
  129. next();
  130. };
  131. }
  132. // First, let's run the before script
  133. if (!this.nolog) debugLog("migrate begin before version #" + migration.version);
  134. migration.before(function () {
  135. if (!this.nolog) debugLog("migrate done before version #" + migration.version);
  136. if (!this.nolog) debugLog("migrate begin migrate version #" + migration.version);
  137. migration.migrate(transaction, function () {
  138. if (!this.nolog) debugLog("migrate done migrate version #" + migration.version);
  139. // Migration successfully appliedn let's go to the next one!
  140. if (!this.nolog) debugLog("migrate begin after version #" + migration.version);
  141. migration.after(function () {
  142. if (!this.nolog) debugLog("migrate done after version #" + migration.version);
  143. if (!this.nolog) debugLog("Migrated to " + migration.version);
  144. //last modification occurred, need finish
  145. if(migrations.length ==0) {
  146. if (!this.nolog) {
  147. debugLog("migrate setting transaction.oncomplete to finish version #" + migration.version);
  148. transaction.oncomplete = function() {
  149. debugLog("migrate done transaction.oncomplete version #" + migration.version);
  150. debugLog("Done migrating");
  151. }
  152. }
  153. }
  154. else
  155. {
  156. if (!this.nolog) debugLog("migrate end from version #" + version + " to " + migration.version);
  157. that.migrate(transaction, migrations, version, options);
  158. }
  159. }.bind(this));
  160. }.bind(this));
  161. }.bind(this));
  162. } else {
  163. // No need to apply this migration
  164. if (!this.nolog) debugLog("Skipping migration " + migration.version);
  165. this.migrate(transaction, migrations, version, options);
  166. }
  167. }
  168. },
  169. // This is the main method, called by the ExecutionQueue when the driver is ready (database open and migration performed)
  170. execute: function (storeName, method, object, options) {
  171. if (!this.nolog) debugLog("execute : " + method + " on " + storeName + " for " + object.id);
  172. switch (method) {
  173. case "create":
  174. this.create(storeName, object, options);
  175. break;
  176. case "read":
  177. if (object.id || object.cid) {
  178. this.read(storeName, object, options); // It's a model
  179. } else {
  180. this.query(storeName, object, options); // It's a collection
  181. }
  182. break;
  183. case "update":
  184. this.update(storeName, object, options); // We may want to check that this is not a collection. TOFIX
  185. break;
  186. case "delete":
  187. if (object.id || object.cid) {
  188. this.delete(storeName, object, options);
  189. } else {
  190. this.clear(storeName, object, options);
  191. }
  192. break;
  193. default:
  194. // Hum what?
  195. }
  196. },
  197. // Writes the json to the storeName in db. It is a create operations, which means it will fail if the key already exists
  198. // options are just success and error callbacks.
  199. create: function (storeName, object, options) {
  200. var writeTransaction = this.db.transaction([storeName], 'readwrite');
  201. //this._track_transaction(writeTransaction);
  202. var store = writeTransaction.objectStore(storeName);
  203. var json = object.toJSON();
  204. var idAttribute = _.result(object, 'idAttribute');
  205. var writeRequest;
  206. if (json[idAttribute] === undefined && !store.autoIncrement) json[idAttribute] = guid();
  207. writeTransaction.onerror = function (e) {
  208. options.error(e);
  209. };
  210. writeTransaction.oncomplete = function (e) {
  211. options.success(json);
  212. };
  213. if (!store.keyPath)
  214. writeRequest = store.add(json, json[idAttribute]);
  215. else
  216. writeRequest = store.add(json);
  217. },
  218. // Writes the json to the storeName in db. It is an update operation, which means it will overwrite the value if the key already exist
  219. // options are just success and error callbacks.
  220. update: function (storeName, object, options) {
  221. var writeTransaction = this.db.transaction([storeName], 'readwrite');
  222. //this._track_transaction(writeTransaction);
  223. var store = writeTransaction.objectStore(storeName);
  224. var json = object.toJSON();
  225. var idAttribute = _.result(object, 'idAttribute');
  226. var writeRequest;
  227. if (!json[idAttribute]) json[idAttribute] = guid();
  228. if (!store.keyPath)
  229. writeRequest = store.put(json, json[idAttribute]);
  230. else
  231. writeRequest = store.put(json);
  232. writeRequest.onerror = function (e) {
  233. options.error(e);
  234. };
  235. writeTransaction.oncomplete = function (e) {
  236. options.success(json);
  237. };
  238. },
  239. // Reads from storeName in db with json.id if it's there of with any json.xxxx as long as xxx is an index in storeName
  240. read: function (storeName, object, options) {
  241. var readTransaction = this.db.transaction([storeName], "readonly");
  242. this._track_transaction(readTransaction);
  243. var store = readTransaction.objectStore(storeName);
  244. var json = object.toJSON();
  245. var idAttribute = _.result(object, 'idAttribute');
  246. var getRequest = null;
  247. if (json[idAttribute]) {
  248. getRequest = store.get(json[idAttribute]);
  249. } else if(options.index) {
  250. var index = store.index(options.index.name);
  251. getRequest = index.get(options.index.value);
  252. } else {
  253. // We need to find which index we have
  254. var cardinality = 0; // try to fit the index with most matches
  255. _.each(store.indexNames, function (key, index) {
  256. index = store.index(key);
  257. if(typeof index.keyPath === 'string' && 1 > cardinality) {
  258. // simple index
  259. if (json[index.keyPath] !== undefined) {
  260. getRequest = index.get(json[index.keyPath]);
  261. cardinality = 1;
  262. }
  263. } else if(typeof index.keyPath === 'object' && index.keyPath.length > cardinality) {
  264. // compound index
  265. var valid = true;
  266. var keyValue = _.map(index.keyPath, function(keyPart) {
  267. valid = valid && json[keyPart] !== undefined;
  268. return json[keyPart];
  269. });
  270. if(valid) {
  271. getRequest = index.get(keyValue);
  272. cardinality = index.keyPath.length;
  273. }
  274. }
  275. });
  276. }
  277. if (getRequest) {
  278. getRequest.onsuccess = function (event) {
  279. if (event.target.result) {
  280. options.success(event.target.result);
  281. } else {
  282. options.error("Not Found");
  283. }
  284. };
  285. getRequest.onerror = function () {
  286. options.error("Not Found"); // We couldn't find the record.
  287. }
  288. } else {
  289. options.error("Not Found"); // We couldn't even look for it, as we don't have enough data.
  290. }
  291. },
  292. // Deletes the json.id key and value in storeName from db.
  293. delete: function (storeName, object, options) {
  294. var deleteTransaction = this.db.transaction([storeName], 'readwrite');
  295. //this._track_transaction(deleteTransaction);
  296. var store = deleteTransaction.objectStore(storeName);
  297. var json = object.toJSON();
  298. var idAttribute = _.result(object, 'idAttribute');
  299. var deleteRequest = store.delete(json[idAttribute]);
  300. deleteTransaction.oncomplete = function (event) {
  301. options.success(null);
  302. };
  303. deleteRequest.onerror = function (event) {
  304. options.error("Not Deleted");
  305. };
  306. },
  307. // Clears all records for storeName from db.
  308. clear: function (storeName, object, options) {
  309. var deleteTransaction = this.db.transaction([storeName], "readwrite");
  310. //this._track_transaction(deleteTransaction);
  311. var store = deleteTransaction.objectStore(storeName);
  312. var deleteRequest = store.clear();
  313. deleteRequest.onsuccess = function (event) {
  314. options.success(null);
  315. };
  316. deleteRequest.onerror = function (event) {
  317. options.error("Not Cleared");
  318. };
  319. },
  320. // Performs a query on storeName in db.
  321. // options may include :
  322. // - conditions : value of an index, or range for an index
  323. // - range : range for the primary key
  324. // - limit : max number of elements to be yielded
  325. // - offset : skipped items.
  326. query: function (storeName, collection, options) {
  327. var elements = [];
  328. var skipped = 0, processed = 0;
  329. var queryTransaction = this.db.transaction([storeName], "readonly");
  330. //this._track_transaction(queryTransaction);
  331. var idAttribute = _.result(collection.model.prototype, 'idAttribute');
  332. var readCursor = null;
  333. var store = queryTransaction.objectStore(storeName);
  334. var index = null,
  335. lower = null,
  336. upper = null,
  337. bounds = null;
  338. if (options.conditions) {
  339. // We have a condition, we need to use it for the cursor
  340. _.each(store.indexNames, function (key) {
  341. if (!readCursor) {
  342. index = store.index(key);
  343. if (options.conditions[index.keyPath] instanceof Array) {
  344. lower = options.conditions[index.keyPath][0] > options.conditions[index.keyPath][1] ? options.conditions[index.keyPath][1] : options.conditions[index.keyPath][0];
  345. upper = options.conditions[index.keyPath][0] > options.conditions[index.keyPath][1] ? options.conditions[index.keyPath][0] : options.conditions[index.keyPath][1];
  346. bounds = IDBKeyRange.bound(lower, upper, true, true);
  347. if (options.conditions[index.keyPath][0] > options.conditions[index.keyPath][1]) {
  348. // Looks like we want the DESC order
  349. readCursor = index.openCursor(bounds, window.IDBCursor.PREV || "prev");
  350. } else {
  351. // We want ASC order
  352. readCursor = index.openCursor(bounds, window.IDBCursor.NEXT || "next");
  353. }
  354. } else if (typeof options.conditions[index.keyPath] === 'object' && ('$gt' in options.conditions[index.keyPath] || '$gte' in options.conditions[index.keyPath])) {
  355. if('$gt' in options.conditions[index.keyPath])
  356. bounds = IDBKeyRange.lowerBound(options.conditions[index.keyPath]['$gt'], true);
  357. else
  358. bounds = IDBKeyRange.lowerBound(options.conditions[index.keyPath]['$gte']);
  359. readCursor = index.openCursor(bounds, window.IDBCursor.NEXT || "next");
  360. } else if (typeof options.conditions[index.keyPath] === 'object' && ('$lt' in options.conditions[index.keyPath] || '$lte' in options.conditions[index.keyPath])) {
  361. if('$lt' in options.conditions[index.keyPath])
  362. bounds = IDBKeyRange.upperBound(options.conditions[index.keyPath]['$lt'], true);
  363. else
  364. bounds = IDBKeyRange.upperBound(options.conditions[index.keyPath]['$lte']);
  365. readCursor = index.openCursor(bounds, window.IDBCursor.NEXT || "next");
  366. } else if (options.conditions[index.keyPath] != undefined) {
  367. bounds = IDBKeyRange.only(options.conditions[index.keyPath]);
  368. readCursor = index.openCursor(bounds);
  369. }
  370. }
  371. });
  372. } else if (options.index) {
  373. index = store.index(options.index.name);
  374. var excludeLower = !!options.index.excludeLower;
  375. var excludeUpper = !!options.index.excludeUpper;
  376. if (index) {
  377. if (options.index.lower && options.index.upper) {
  378. bounds = IDBKeyRange.bound(options.index.lower, options.index.upper, excludeLower, excludeUpper);
  379. } else if (options.index.lower) {
  380. bounds = IDBKeyRange.lowerBound(options.index.lower, excludeLower);
  381. } else if (options.index.upper) {
  382. bounds = IDBKeyRange.upperBound(options.index.upper, excludeUpper);
  383. } else if (options.index.only) {
  384. bounds = IDBKeyRange.only(options.index.only);
  385. }
  386. if (typeof options.index.order === 'string' && options.index.order.toLowerCase() === 'desc') {
  387. readCursor = index.openCursor(bounds, window.IDBCursor.PREV || "prev");
  388. } else {
  389. readCursor = index.openCursor(bounds, window.IDBCursor.NEXT || "next");
  390. }
  391. }
  392. } else {
  393. // No conditions, use the index
  394. if (options.range) {
  395. lower = options.range[0] > options.range[1] ? options.range[1] : options.range[0];
  396. upper = options.range[0] > options.range[1] ? options.range[0] : options.range[1];
  397. bounds = IDBKeyRange.bound(lower, upper);
  398. if (options.range[0] > options.range[1]) {
  399. readCursor = store.openCursor(bounds, window.IDBCursor.PREV || "prev");
  400. } else {
  401. readCursor = store.openCursor(bounds, window.IDBCursor.NEXT || "next");
  402. }
  403. } else {
  404. readCursor = store.openCursor();
  405. }
  406. }
  407. if (typeof (readCursor) == "undefined" || !readCursor) {
  408. options.error("No Cursor");
  409. } else {
  410. readCursor.onerror = function(e){
  411. options.error("readCursor error", e);
  412. };
  413. // Setup a handler for the cursor’s `success` event:
  414. readCursor.onsuccess = function (e) {
  415. var cursor = e.target.result;
  416. if (!cursor) {
  417. if (options.addIndividually || options.clear) {
  418. // nothing!
  419. // We need to indicate that we're done. But, how?
  420. collection.trigger("reset");
  421. } else {
  422. options.success(elements); // We're done. No more elements.
  423. }
  424. }
  425. else {
  426. // Cursor is not over yet.
  427. if (options.limit && processed >= options.limit) {
  428. // Yet, we have processed enough elements. So, let's just skip.
  429. if (bounds) {
  430. if (options.conditions && options.conditions[index.keyPath]) {
  431. cursor.continue(options.conditions[index.keyPath][1] + 1); /* We need to 'terminate' the cursor cleany, by moving to the end */
  432. } else if (options.index && (options.index.upper || options.index.lower)) {
  433. if (typeof options.index.order === 'string' && options.index.order.toLowerCase() === 'desc') {
  434. cursor.continue(options.index.lower);
  435. } else {
  436. cursor.continue(options.index.upper);
  437. }
  438. }
  439. } else {
  440. cursor.continue(); /* We need to 'terminate' the cursor cleany, by moving to the end */
  441. }
  442. }
  443. else if (options.offset && options.offset > skipped) {
  444. skipped++;
  445. cursor.continue(); /* We need to Moving the cursor forward */
  446. } else {
  447. // This time, it looks like it's good!
  448. if (options.addIndividually) {
  449. collection.add(cursor.value);
  450. } else if (options.clear) {
  451. var deleteRequest = store.delete(cursor.value[idAttribute]);
  452. deleteRequest.onsuccess = function (event) {
  453. elements.push(cursor.value);
  454. };
  455. deleteRequest.onerror = function (event) {
  456. elements.push(cursor.value);
  457. };
  458. } else {
  459. elements.push(cursor.value);
  460. }
  461. processed++;
  462. cursor.continue();
  463. }
  464. }
  465. };
  466. }
  467. },
  468. close :function(){
  469. if(this.db){
  470. this.db.close();
  471. }
  472. }
  473. };
  474. // ExecutionQueue object
  475. // The execution queue is an abstraction to buffer up requests to the database.
  476. // It holds a "driver". When the driver is ready, it just fires up the queue and executes in sync.
  477. function ExecutionQueue(schema,next,nolog) {
  478. this.driver = new Driver(schema, this.ready.bind(this), nolog, this.error.bind(this));
  479. this.started = false;
  480. this.failed = false;
  481. this.stack = [];
  482. this.version = _.last(schema.migrations).version;
  483. this.next = next;
  484. }
  485. // ExecutionQueue Prototype
  486. ExecutionQueue.prototype = {
  487. // Called when the driver is ready
  488. // It just loops over the elements in the queue and executes them.
  489. ready: function () {
  490. this.started = true;
  491. _.each(this.stack, function (message) {
  492. this.execute(message);
  493. }.bind(this));
  494. this.stack = []; // fix memory leak
  495. this.next();
  496. },
  497. error: function() {
  498. this.failed = true;
  499. _.each(this.stack, function (message) {
  500. this.execute(message);
  501. }.bind(this));
  502. this.stack = [];
  503. this.next();
  504. },
  505. // Executes a given command on the driver. If not started, just stacks up one more element.
  506. execute: function (message) {
  507. if (this.started) {
  508. try {
  509. this.driver.execute(message[2].storeName || message[1].storeName, message[0], message[1], message[2]); // Upon messages, we execute the query
  510. } catch (e) {
  511. if (e.name === 'InvalidStateError') {
  512. var f = window.onInvalidStateError;
  513. if (f) f(e);
  514. }
  515. throw e;
  516. }
  517. } else if (this.failed) {
  518. message[2].error();
  519. } else {
  520. this.stack.push(message);
  521. }
  522. },
  523. close : function(){
  524. this.driver.close();
  525. }
  526. };
  527. // Method used by Backbone for sync of data with data store. It was initially designed to work with "server side" APIs, This wrapper makes
  528. // it work with the local indexedDB stuff. It uses the schema attribute provided by the object.
  529. // The wrapper keeps an active Executuon Queue for each "schema", and executes querues agains it, based on the object type (collection or
  530. // single model), but also the method... etc.
  531. // Keeps track of the connections
  532. var Databases = {};
  533. function sync(method, object, options) {
  534. if(method == "closeall"){
  535. _.each(Databases,function(database){
  536. database.close();
  537. });
  538. // Clean up active databases object.
  539. Databases = {};
  540. return Backbone.$.Deferred().resolve();
  541. }
  542. // If a model or a collection does not define a database, fall back on ajaxSync
  543. if (!object || !_.isObject(object.database)) {
  544. return Backbone.ajaxSync(method, object, options);
  545. }
  546. var schema = object.database;
  547. if (Databases[schema.id]) {
  548. if(Databases[schema.id].version != _.last(schema.migrations).version){
  549. Databases[schema.id].close();
  550. delete Databases[schema.id];
  551. }
  552. }
  553. var promise;
  554. if (typeof Backbone.$ === 'undefined' || typeof Backbone.$.Deferred === 'undefined') {
  555. var noop = function() {};
  556. var resolve = noop;
  557. var reject = noop;
  558. } else {
  559. var dfd = Backbone.$.Deferred();
  560. var resolve = dfd.resolve;
  561. var reject = dfd.reject;
  562. promise = dfd.promise();
  563. }
  564. var success = options.success;
  565. options.success = function(resp) {
  566. if (success) success(resp);
  567. resolve();
  568. object.trigger('sync', object, resp, options);
  569. };
  570. var error = options.error;
  571. options.error = function(resp) {
  572. if (error) error(resp);
  573. reject();
  574. object.trigger('error', object, resp, options);
  575. };
  576. var next = function(){
  577. Databases[schema.id].execute([method, object, options]);
  578. };
  579. if (!Databases[schema.id]) {
  580. Databases[schema.id] = new ExecutionQueue(schema,next,schema.nolog);
  581. } else {
  582. next();
  583. }
  584. return promise;
  585. };
  586. Backbone.ajaxSync = Backbone.sync;
  587. Backbone.sync = sync;
  588. return { sync: sync, debugLog: debugLog};
  589. }));