|
@@ -0,0 +1,687 @@
|
|
|
+/*! *****************************************************************************
|
|
|
+Copyright (c) Microsoft Corporation.
|
|
|
+
|
|
|
+Permission to use, copy, modify, and/or distribute this software for any
|
|
|
+purpose with or without fee is hereby granted.
|
|
|
+
|
|
|
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
|
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
|
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
|
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
|
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
|
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
|
+PERFORMANCE OF THIS SOFTWARE.
|
|
|
+***************************************************************************** */
|
|
|
+
|
|
|
+var __assign = function() {
|
|
|
+ __assign = Object.assign || function __assign(t) {
|
|
|
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
|
+ s = arguments[i];
|
|
|
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
|
|
|
+ }
|
|
|
+ return t;
|
|
|
+ };
|
|
|
+ return __assign.apply(this, arguments);
|
|
|
+};
|
|
|
+
|
|
|
+function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
|
+ return new (P || (P = Promise))(function (resolve, reject) {
|
|
|
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
|
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
|
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
|
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function __generator(thisArg, body) {
|
|
|
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
|
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
|
+ function verb(n) { return function (v) { return step([n, v]); }; }
|
|
|
+ function step(op) {
|
|
|
+ if (f) throw new TypeError("Generator is already executing.");
|
|
|
+ while (_) try {
|
|
|
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
|
+ if (y = 0, t) op = [op[0] & 2, t.value];
|
|
|
+ switch (op[0]) {
|
|
|
+ case 0: case 1: t = op; break;
|
|
|
+ case 4: _.label++; return { value: op[1], done: false };
|
|
|
+ case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
|
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
|
+ default:
|
|
|
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
|
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
|
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
|
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
|
+ if (t[2]) _.ops.pop();
|
|
|
+ _.trys.pop(); continue;
|
|
|
+ }
|
|
|
+ op = body.call(thisArg, _);
|
|
|
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
|
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// TODO: GLOBAL ERROR HANDLER
|
|
|
+// TODO: optimizing for duplicated codes, make a global promise to handle error
|
|
|
+// TODO: make sure that schema is sync with objectStore
|
|
|
+var GoDBTable = /** @class */ (function () {
|
|
|
+ function GoDBTable(godb, name, schema) {
|
|
|
+ this.godb = godb;
|
|
|
+ this.name = name;
|
|
|
+ this.schema = schema || null;
|
|
|
+ }
|
|
|
+ // crud(operation: GoDBTableCRUD): Promise<void | GoDBData | GoDBData[]> {
|
|
|
+ // return new Promise((resolve, reject) => {
|
|
|
+ // this.godb.getDB((idb) => {
|
|
|
+ // try {
|
|
|
+ // } catch (err) {
|
|
|
+ // reject(err);
|
|
|
+ // }
|
|
|
+ // });
|
|
|
+ // });
|
|
|
+ // }
|
|
|
+ // TODO: check if criteria's key fits schema
|
|
|
+ GoDBTable.prototype.get = function (criteria) {
|
|
|
+ var _this = this;
|
|
|
+ return new Promise(function (resolve, reject) {
|
|
|
+ _this.godb.getDB(function (idb) {
|
|
|
+ try {
|
|
|
+ var store = idb
|
|
|
+ .transaction(_this.name, 'readonly')
|
|
|
+ .objectStore(_this.name);
|
|
|
+ // if the key is not unique, return one object with least id
|
|
|
+ // TODO: warning user if the key is not unique
|
|
|
+ var onSuccess = function (e) {
|
|
|
+ var result = e.target.result;
|
|
|
+ resolve(result);
|
|
|
+ };
|
|
|
+ var onError = function (e) {
|
|
|
+ var error = e.target.error;
|
|
|
+ reject(error);
|
|
|
+ };
|
|
|
+ if (typeof criteria === 'object') {
|
|
|
+ var key = Object.keys(criteria)[0];
|
|
|
+ var value = criteria[key];
|
|
|
+ var request = key === 'id'
|
|
|
+ ? store.get(value)
|
|
|
+ : store.index(key).get(value);
|
|
|
+ request.onsuccess = onSuccess;
|
|
|
+ request.onerror = onError;
|
|
|
+ }
|
|
|
+ else if (typeof criteria === 'number') {
|
|
|
+ var request = store.get(criteria);
|
|
|
+ request.onsuccess = onSuccess;
|
|
|
+ request.onerror = onError;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ reject(new Error('Table.get() failed: invalid criteria'));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ };
|
|
|
+ GoDBTable.prototype.getAll = function (limit) {
|
|
|
+ var _this = this;
|
|
|
+ return new Promise(function (resolve, reject) {
|
|
|
+ _this.godb.getDB(function (idb) {
|
|
|
+ try {
|
|
|
+ var store = idb
|
|
|
+ .transaction(_this.name, 'readonly')
|
|
|
+ .objectStore(_this.name);
|
|
|
+ var request = limit
|
|
|
+ ? store.getAll(null, limit)
|
|
|
+ : store.getAll();
|
|
|
+ request.onsuccess = function (e) {
|
|
|
+ var result = e.target.result;
|
|
|
+ resolve(result);
|
|
|
+ };
|
|
|
+ request.onerror = function (e) {
|
|
|
+ var error = e.target.error;
|
|
|
+ reject(error);
|
|
|
+ };
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ };
|
|
|
+ // TODO: check data's schema
|
|
|
+ // resolve: id of added item
|
|
|
+ GoDBTable.prototype.add = function (data) {
|
|
|
+ var _this = this;
|
|
|
+ return new Promise(function (resolve, reject) {
|
|
|
+ _this.godb.getDB(function (idb) {
|
|
|
+ try {
|
|
|
+ var store = idb
|
|
|
+ .transaction(_this.name, 'readwrite')
|
|
|
+ .objectStore(_this.name);
|
|
|
+ var request = store.add(data);
|
|
|
+ // TODO: according to MDN, `onsuccess` does not exactly mean a successful adding
|
|
|
+ request.onsuccess = function (e) {
|
|
|
+ var result = e.target.result;
|
|
|
+ resolve(__assign(__assign({}, data), { id: result }));
|
|
|
+ };
|
|
|
+ request.onerror = function (e) {
|
|
|
+ var error = e.target.error;
|
|
|
+ reject(error);
|
|
|
+ };
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ };
|
|
|
+ // TODO FIX: the order might be unexpected when
|
|
|
+ // `addMany` and `add` were executing at the same time
|
|
|
+ GoDBTable.prototype.addMany = function (data) {
|
|
|
+ var _this = this;
|
|
|
+ return new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
|
|
|
+ var arr, _i, data_1, item, _a, _b;
|
|
|
+ return __generator(this, function (_c) {
|
|
|
+ switch (_c.label) {
|
|
|
+ case 0:
|
|
|
+ if (!Array.isArray(data)) return [3 /*break*/, 5];
|
|
|
+ arr = [];
|
|
|
+ _i = 0, data_1 = data;
|
|
|
+ _c.label = 1;
|
|
|
+ case 1:
|
|
|
+ if (!(_i < data_1.length)) return [3 /*break*/, 4];
|
|
|
+ item = data_1[_i];
|
|
|
+ _b = (_a = arr).push;
|
|
|
+ return [4 /*yield*/, this.add(item)];
|
|
|
+ case 2:
|
|
|
+ _b.apply(_a, [_c.sent()]);
|
|
|
+ _c.label = 3;
|
|
|
+ case 3:
|
|
|
+ _i++;
|
|
|
+ return [3 /*break*/, 1];
|
|
|
+ case 4:
|
|
|
+ resolve(arr);
|
|
|
+ return [3 /*break*/, 6];
|
|
|
+ case 5:
|
|
|
+ reject(new Error('Table.addMany() failed: input data should be an array'));
|
|
|
+ _c.label = 6;
|
|
|
+ case 6: return [2 /*return*/];
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }); });
|
|
|
+ };
|
|
|
+ // TODO: check data's schema
|
|
|
+ // if data is not in table, `put` will add the data, otherwise update
|
|
|
+ // TODO: check schema's unique key, which decides whether update or add data
|
|
|
+ // resolve: id of updated item
|
|
|
+ GoDBTable.prototype.put = function (data) {
|
|
|
+ var _this = this;
|
|
|
+ return new Promise(function (resolve, reject) {
|
|
|
+ _this.godb.getDB(function (idb) {
|
|
|
+ if (!(data && typeof data === 'object'))
|
|
|
+ return reject(new Error('Table.put() failed: data should be an object'));
|
|
|
+ if (!data.id)
|
|
|
+ return reject(new Error('Table.put() failed: id is required in data'));
|
|
|
+ try {
|
|
|
+ var store = idb
|
|
|
+ .transaction(_this.name, 'readwrite')
|
|
|
+ .objectStore(_this.name);
|
|
|
+ // equals to `add(data)` if `data` is not in table
|
|
|
+ var request = store.put(data);
|
|
|
+ // TODO: according to MDN, `onsuccess` does not exactly mean a successful adding
|
|
|
+ request.onsuccess = function (e) {
|
|
|
+ var result = e.target.result;
|
|
|
+ resolve(__assign(__assign({}, data), { id: result }));
|
|
|
+ };
|
|
|
+ request.onerror = function (e) {
|
|
|
+ var error = e.target.error;
|
|
|
+ reject(error);
|
|
|
+ };
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ };
|
|
|
+ // update only, be ware of the difference to `put`
|
|
|
+ GoDBTable.prototype.update = function () {
|
|
|
+ };
|
|
|
+ GoDBTable.prototype["delete"] = function (criteria) {
|
|
|
+ var _this = this;
|
|
|
+ return new Promise(function (resolve, reject) {
|
|
|
+ _this.godb.getDB(function (idb) {
|
|
|
+ try {
|
|
|
+ _this.get(criteria).then(function (doc) {
|
|
|
+ var store = idb
|
|
|
+ .transaction(_this.name, 'readwrite')
|
|
|
+ .objectStore(_this.name);
|
|
|
+ var request = store["delete"](doc.id);
|
|
|
+ request.onsuccess = function () {
|
|
|
+ resolve();
|
|
|
+ };
|
|
|
+ request.onerror = function (e) {
|
|
|
+ var error = e.target.error;
|
|
|
+ reject(error);
|
|
|
+ };
|
|
|
+ });
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ };
|
|
|
+ // find by a function
|
|
|
+ GoDBTable.prototype.find = function (fn) {
|
|
|
+ var _this = this;
|
|
|
+ return new Promise(function (resolve, reject) {
|
|
|
+ _this.godb.getDB(function (idb) {
|
|
|
+ try {
|
|
|
+ var store = idb
|
|
|
+ .transaction(_this.name, 'readonly')
|
|
|
+ .objectStore(_this.name);
|
|
|
+ store.openCursor().onsuccess = function (e) {
|
|
|
+ var cursor = e.target.result;
|
|
|
+ if (cursor) {
|
|
|
+ if (fn(cursor.value))
|
|
|
+ return resolve(cursor.value);
|
|
|
+ cursor["continue"]();
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ resolve(null);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ };
|
|
|
+ // return all results by a find function
|
|
|
+ GoDBTable.prototype.findAll = function (fn) {
|
|
|
+ var _this = this;
|
|
|
+ return new Promise(function (resolve, reject) {
|
|
|
+ _this.godb.getDB(function (idb) {
|
|
|
+ try {
|
|
|
+ var store = idb
|
|
|
+ .transaction(_this.name, 'readonly')
|
|
|
+ .objectStore(_this.name);
|
|
|
+ var data_2 = [];
|
|
|
+ store.openCursor().onsuccess = function (e) {
|
|
|
+ var cursor = e.target.result;
|
|
|
+ if (cursor) {
|
|
|
+ if (fn(cursor.value))
|
|
|
+ data_2.push(cursor.value);
|
|
|
+ cursor["continue"]();
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ resolve(data_2);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ };
|
|
|
+ // TODO: people.where('age').below(22).toArray()
|
|
|
+ GoDBTable.prototype.where = function () {
|
|
|
+ };
|
|
|
+ // showing 1000 items maximum in Chrome and Firefox
|
|
|
+ GoDBTable.prototype.consoleTable = function (limit) {
|
|
|
+ var _this = this;
|
|
|
+ if (limit === void 0) { limit = 1000; }
|
|
|
+ return new Promise(function (resolve, reject) {
|
|
|
+ _this.godb.getDB(function (idb) {
|
|
|
+ try {
|
|
|
+ if (limit > 1000) {
|
|
|
+ console.warn("Table.consoleTable() expects a limit no more than 1000");
|
|
|
+ limit = 1000;
|
|
|
+ }
|
|
|
+ var count_1 = 0;
|
|
|
+ var data_3 = {};
|
|
|
+ var store = idb
|
|
|
+ .transaction(_this.name, 'readonly')
|
|
|
+ .objectStore(_this.name);
|
|
|
+ store.openCursor().onsuccess = function (e) {
|
|
|
+ var cursor = e.target.result;
|
|
|
+ if (cursor && count_1 < limit) {
|
|
|
+ count_1 += 1;
|
|
|
+ data_3[cursor.key] = __assign({}, cursor.value);
|
|
|
+ delete data_3[cursor.key].id;
|
|
|
+ cursor["continue"]();
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ console.log("Data in Table['" + _this.name + "'] of Database['" + _this.godb.name + "'], limit " + limit + ":");
|
|
|
+ console.table(data_3);
|
|
|
+ resolve();
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ };
|
|
|
+ return GoDBTable;
|
|
|
+}());
|
|
|
+
|
|
|
+var global = window;
|
|
|
+var indexedDB = global.indexedDB ||
|
|
|
+ global.webkitIndexedDB ||
|
|
|
+ global.mozIndexedDB ||
|
|
|
+ global.msIndexedDB;
|
|
|
+
|
|
|
+var GoDB = /** @class */ (function () {
|
|
|
+ function GoDB(name, schema, config) {
|
|
|
+ // init params
|
|
|
+ this.version = 0;
|
|
|
+ this.tables = {};
|
|
|
+ this._callbackQueue = [];
|
|
|
+ // save database's name
|
|
|
+ this.name = name;
|
|
|
+ // settings
|
|
|
+ if (config) {
|
|
|
+ var version = config.version;
|
|
|
+ if (version)
|
|
|
+ this.version = version;
|
|
|
+ }
|
|
|
+ // init tables, `this.idb` is null when init
|
|
|
+ this.updateSchema(schema);
|
|
|
+ // open connection to the IndexedDB
|
|
|
+ this.getDB();
|
|
|
+ }
|
|
|
+ GoDB.prototype.table = function (table, tableSchema) {
|
|
|
+ if (!this.tables[table]) {
|
|
|
+ this.tables[table] = new GoDBTable(this, table, tableSchema);
|
|
|
+ this.updateSchema();
|
|
|
+ }
|
|
|
+ else if (tableSchema) {
|
|
|
+ this.tables[table] = new GoDBTable(this, table, tableSchema);
|
|
|
+ if (this._shouldUpgrade())
|
|
|
+ this.updateSchema();
|
|
|
+ }
|
|
|
+ return this.tables[table];
|
|
|
+ };
|
|
|
+ GoDB.prototype.updateSchema = function (schema) {
|
|
|
+ if (schema) {
|
|
|
+ this.tables = {};
|
|
|
+ for (var table in schema)
|
|
|
+ this.tables[table] = new GoDBTable(this, table, schema[table]);
|
|
|
+ }
|
|
|
+ if (this.idb) {
|
|
|
+ // create new objectStores when database is already opened
|
|
|
+ console.log("Updating Schema of Database['" + this.name + "']");
|
|
|
+ this.idb.close();
|
|
|
+ // activate callbackQueue in getDB()
|
|
|
+ // and avoid repeating calling _openDB() before db's opening
|
|
|
+ this.idb = null;
|
|
|
+ // triger `onupgradeneeded` event in _openDB() to update objectStores
|
|
|
+ this._openDB(++this.version, true);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ GoDB.prototype.close = function () {
|
|
|
+ if (this.idb) {
|
|
|
+ this.idb.close();
|
|
|
+ this.idb = null;
|
|
|
+ this._connection = null;
|
|
|
+ this._closed = true;
|
|
|
+ console.log("A connection to Database['" + this.name + "'] is closed");
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ console.warn("Unable to close Database['" + this.name + "']: it is not opened yet");
|
|
|
+ }
|
|
|
+ };
|
|
|
+ // drop a database
|
|
|
+ GoDB.prototype.drop = function () {
|
|
|
+ var _this = this;
|
|
|
+ return new Promise(function (resolve, reject) {
|
|
|
+ var database = _this.name;
|
|
|
+ _this.close();
|
|
|
+ // no need to handle Exception according to MDN
|
|
|
+ var deleteRequest = indexedDB.deleteDatabase(database);
|
|
|
+ deleteRequest.onsuccess = function (ev) {
|
|
|
+ _this.version = 0;
|
|
|
+ console.log("Database['" + database + "'] is successfully dropped");
|
|
|
+ if (_this.onClosed) {
|
|
|
+ if (typeof _this.onClosed === 'function')
|
|
|
+ _this.onClosed();
|
|
|
+ else
|
|
|
+ console.warn("'onClosed' should be a function, not " + typeof _this.onClosed);
|
|
|
+ _this.onClosed = null;
|
|
|
+ }
|
|
|
+ resolve(ev);
|
|
|
+ };
|
|
|
+ deleteRequest.onerror = function (ev) {
|
|
|
+ var error = ev.target.error;
|
|
|
+ var name = error.name, message = error.message;
|
|
|
+ console.warn("Unable to drop Database['" + database + "'] \n- " + name + ": " + message);
|
|
|
+ reject(name + ": " + message);
|
|
|
+ };
|
|
|
+ });
|
|
|
+ };
|
|
|
+ GoDB.prototype.getDBState = function () {
|
|
|
+ if (this._closed)
|
|
|
+ return 'closed';
|
|
|
+ if (this.idb)
|
|
|
+ return 'opened';
|
|
|
+ if (this._connection)
|
|
|
+ return 'connecting';
|
|
|
+ return 'init';
|
|
|
+ };
|
|
|
+ /**
|
|
|
+ * It is necessary to get `IDBDatabase` instance (`this.idb`) before
|
|
|
+ * table operations like table.add(), table.get(), etc.
|
|
|
+ * that's why a `getDB` function was needed in this class
|
|
|
+ *
|
|
|
+ * There are 4 possible states when calling `getDB`:
|
|
|
+ *
|
|
|
+ * State `closed`: database is closed or dropped by user
|
|
|
+ * Operation: warning user in console, not calling callback
|
|
|
+ * Where: After invoking `this.close()` or `this.drop()`
|
|
|
+ *
|
|
|
+ * State `init`: no connection yet (`this.connection` is undefined)
|
|
|
+ * Operation: open connection to the IndexedDB database
|
|
|
+ * Where: Constructor's `this.getDB()`, only executed once
|
|
|
+ *
|
|
|
+ * State `connecting`: connection opened, but db is not opened yet (`this.idb` is undefined)
|
|
|
+ * Operation: push the table operations to a queue, executing them when db is opened
|
|
|
+ * Where: Table operations that are in the same macrotask as `new GoDB()`
|
|
|
+ *
|
|
|
+ * State `opened`: The database is opened
|
|
|
+ * Operation: Invoking the callback synchronously with `this.idb`
|
|
|
+ * Where: Table operations when the db is opened,
|
|
|
+ * mention that the db's opening only takes a few milliseconds,
|
|
|
+ * and normally most of user's table operations will happen after db's opening
|
|
|
+ *
|
|
|
+ * State sequence:
|
|
|
+ * init -> connecting -> opened -> closed
|
|
|
+ *
|
|
|
+ * Attention: callback is async-called in state `init` and `connecting`,
|
|
|
+ * but being sync-called in state `opened`
|
|
|
+ */
|
|
|
+ GoDB.prototype.getDB = function (callback) {
|
|
|
+ // State `opened`: db is opened, calling callback synchronously with IDBDatabase instance
|
|
|
+ if (this.idb) {
|
|
|
+ if (callback && typeof callback === 'function')
|
|
|
+ callback(this.idb);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // State `closed`: db is closed
|
|
|
+ if (this._closed) {
|
|
|
+ console.warn("Database['" + this.name + "'] is closed, operations will not be executed");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // State `connecting`: connection is opened, but db is not opened yet
|
|
|
+ if (this._connection) {
|
|
|
+ if (callback && typeof callback === 'function')
|
|
|
+ this._callbackQueue.push(callback);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // State `init`: opening connection to IndexedDB
|
|
|
+ // In case user has set a version in GoDBConfig
|
|
|
+ if (this.version)
|
|
|
+ this._openDB(this.version);
|
|
|
+ else
|
|
|
+ this._openDB();
|
|
|
+ };
|
|
|
+ GoDB.prototype._openDB = function (version, stopUpgrade) {
|
|
|
+ var _this = this;
|
|
|
+ var database = this.name;
|
|
|
+ var tables = this.tables;
|
|
|
+ this._connection = version
|
|
|
+ ? indexedDB.open(database, version)
|
|
|
+ : indexedDB.open(database);
|
|
|
+ this._connection.onsuccess = function (ev) {
|
|
|
+ var result = _this._connection.result
|
|
|
+ || ev.target.result;
|
|
|
+ _this.version = result.version;
|
|
|
+ // triggered when 'onupgradeneeded' was not called
|
|
|
+ // meaning that the database was already existed in browser
|
|
|
+ if (!_this.idb) {
|
|
|
+ _this.idb = result;
|
|
|
+ console.log("Database['" + database + "'] with version (" + result.version + ") is exisiting");
|
|
|
+ }
|
|
|
+ // @ts-ignore
|
|
|
+ for (var _i = 0, _a = _this.idb.objectStoreNames; _i < _a.length; _i++) {
|
|
|
+ var name_1 = _a[_i];
|
|
|
+ if (!_this.tables[name_1])
|
|
|
+ _this.tables[name_1] = new GoDBTable(_this, name_1);
|
|
|
+ }
|
|
|
+ // `stopUpgrade` is used to avoid infinite recursion
|
|
|
+ if (_this._shouldUpgrade() && !stopUpgrade) {
|
|
|
+ // make sure the objectStores structure are matching with the Schema
|
|
|
+ _this.updateSchema();
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ console.log("A connection to Database['" + database + "'] is opening");
|
|
|
+ // executing Table operations invoked by user at State `connecting`
|
|
|
+ if (_this._callbackQueue.length) {
|
|
|
+ _this._callbackQueue.forEach(function (fn) { return fn(_this.idb); });
|
|
|
+ _this._callbackQueue = [];
|
|
|
+ }
|
|
|
+ // call onOpened if it is defined by user
|
|
|
+ if (_this.onOpened) {
|
|
|
+ if (typeof _this.onOpened === 'function')
|
|
|
+ _this.onOpened(_this.idb);
|
|
|
+ else
|
|
|
+ console.warn("'onOpened' should be a function, not " + typeof _this.onOpened);
|
|
|
+ _this.onOpened = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ // called when db version is changing
|
|
|
+ // it is called before `onsuccess`
|
|
|
+ this._connection.onupgradeneeded = function (ev) {
|
|
|
+ var oldVersion = ev.oldVersion, newVersion = ev.newVersion, target = ev.target;
|
|
|
+ var transaction = target.transaction;
|
|
|
+ // get IndexedDB database instance
|
|
|
+ _this.idb = target.result;
|
|
|
+ _this.version = newVersion;
|
|
|
+ if (oldVersion === 0)
|
|
|
+ console.log("Creating Database['" + database + "'] with version (" + newVersion + ")");
|
|
|
+ // make sure the IDB objectStores structure are matching with GoDB Tables' Schema
|
|
|
+ for (var table in tables)
|
|
|
+ _this._updateObjectStore(table, tables[table].schema, transaction);
|
|
|
+ if (oldVersion !== 0)
|
|
|
+ console.log("Database['" + database + "'] version changed from (" + oldVersion + ") to (" + newVersion + ")");
|
|
|
+ };
|
|
|
+ this._connection.onerror = function (ev) {
|
|
|
+ var error = ev.target.error;
|
|
|
+ var name = error.name, message = error.message;
|
|
|
+ throw Error("Failed to open Database['" + database + "']"
|
|
|
+ + ("\n- " + name + ": " + message));
|
|
|
+ };
|
|
|
+ this._connection.onblocked = function (ev) {
|
|
|
+ var _a = ev, newVersion = _a.newVersion, oldVersion = _a.oldVersion;
|
|
|
+ console.warn("Database['" + database + "'] is opening somewhere with version (" + oldVersion + "),", "thus new opening request to version (" + newVersion + ") was blocked.");
|
|
|
+ };
|
|
|
+ };
|
|
|
+ // check if it is needed to update the objectStores in a existing database
|
|
|
+ // return true when objectStores structure are not matching with the schema
|
|
|
+ // - objectStore (table) is not existing
|
|
|
+ // - table schema has one or more index that objectStore do not have
|
|
|
+ // - index properties are different from which defined in schema
|
|
|
+ GoDB.prototype._shouldUpgrade = function () {
|
|
|
+ var idb = this.idb;
|
|
|
+ if (!idb)
|
|
|
+ return false;
|
|
|
+ var tables = this.tables;
|
|
|
+ for (var table in tables) {
|
|
|
+ // check if objectStores is existing
|
|
|
+ if (idb.objectStoreNames.contains(table)) {
|
|
|
+ var transaction = idb.transaction(table, 'readonly');
|
|
|
+ var _a = transaction.objectStore(table), indexNames = _a.indexNames, keyPath = _a.keyPath, autoIncrement = _a.autoIncrement;
|
|
|
+ // if the keyPath is not 'id', or it is not autoIncrement
|
|
|
+ if (keyPath !== 'id' || !autoIncrement) {
|
|
|
+ this.close();
|
|
|
+ throw Error("The current existing objectStore '" + table + "' in IndexedDB '" + this.name + "'"
|
|
|
+ + (" has a " + (autoIncrement ? '' : 'non-') + "autoIncrement keyPath `" + keyPath + "`,")
|
|
|
+ + " while GoDB requires an autoIncrement keyPath `id`,"
|
|
|
+ + (" thus GoDB can not open Database['" + this.name + "']"));
|
|
|
+ }
|
|
|
+ for (var index in tables[table].schema) {
|
|
|
+ // check if the objectStore has the index
|
|
|
+ if (indexNames.contains(index)) ;
|
|
|
+ else {
|
|
|
+ // closing the transaction to avoid db's `blocked` event when upgrading
|
|
|
+ transaction.abort();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ transaction.abort();
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ };
|
|
|
+ // applying the schema to corresponding IndexedDB objectStores to setup indexes
|
|
|
+ // require IDBTransaction `versionchange`
|
|
|
+ // it can only be called in `onupgradeneeded`
|
|
|
+ GoDB.prototype._updateObjectStore = function (table, schema, transaction) {
|
|
|
+ var idb = this.idb;
|
|
|
+ if (!idb)
|
|
|
+ return;
|
|
|
+ var putIndex = function (index, store, update) {
|
|
|
+ if (index === 'id')
|
|
|
+ return;
|
|
|
+ if (update)
|
|
|
+ store.deleteIndex(index);
|
|
|
+ store.createIndex(index, index, {
|
|
|
+ unique: !!schema[index]['unique'],
|
|
|
+ multiEntry: !!schema[index]['multiEntry']
|
|
|
+ });
|
|
|
+ console.log((update ? 'Updating' : 'Creating') + " Index['" + index + "']", "in Table['" + table + "'], Database['" + idb.name + "']");
|
|
|
+ };
|
|
|
+ if (idb.objectStoreNames.contains(table)) {
|
|
|
+ // objectStore is existing, update its indexes
|
|
|
+ var store = transaction.objectStore(table);
|
|
|
+ var indexNames = store.indexNames;
|
|
|
+ // if the objectStore contains an index, update the index
|
|
|
+ // if not, create a new index in the objectStore
|
|
|
+ for (var index in this.tables[table].schema)
|
|
|
+ putIndex(index, store, indexNames.contains(index));
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // create objectStore if not exist
|
|
|
+ var store = idb.createObjectStore(table, {
|
|
|
+ keyPath: 'id',
|
|
|
+ autoIncrement: true
|
|
|
+ });
|
|
|
+ console.log("Creating Table['" + table + "'] in Database['" + idb.name + "']");
|
|
|
+ for (var index in schema)
|
|
|
+ putIndex(index, store);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ return GoDB;
|
|
|
+}());
|
|
|
+
|
|
|
+export default GoDB;
|