1
0
mirror of https://github.com/nextapps-de/flexsearch.git synced 2025-09-25 04:51:29 +02:00
Files
flexsearch/dist/module-debug/db/indexeddb/index.js

632 lines
19 KiB
JavaScript

import { PersistentOptions, SearchResults, EnrichedSearchResults } from "../../type.js";
const VERSION = 1,
IndexedDB = "undefined" != typeof window && (window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB),
IDBTransaction = "undefined" != typeof window && (window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction),
IDBKeyRange = "undefined" != typeof window && (window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange),
fields = ["map", "ctx", "tag", "reg", "cfg"];
import StorageInterface from "../interface.js";
import { toArray } from "../../common.js";
/**
* @param {!string} str
* @return {string}
*/
function sanitize(str) {
return str.toLowerCase().replace(/[^a-z0-9_\-]/g, "");
}
/**
* @param {string|PersistentOptions=} name
* @param {PersistentOptions=} config
* @constructor
* @implements StorageInterface
*/
export default function IdxDB(name, config = {}) {
if (!this) {
return new IdxDB(name, config);
}
if ("object" == typeof name) {
config = /** @type PersistentOptions */name;
name = name.name;
}
if (!name) {
console.info("Default storage space was used, because a name was not passed.");
}
this.id = "flexsearch" + (name ? ":" + sanitize(name) : "");
this.field = config.field ? sanitize(config.field) : "";
this.type = config.type;
this.support_tag_search = !1;
this.fastupdate = !1;
this.db = null;
this.trx = {};
}
IdxDB.prototype.mount = function (flexsearch) {
//if(flexsearch.constructor === Document){
if (!flexsearch.encoder) {
return flexsearch.mount(this);
}
flexsearch.db = this;
return this.open();
};
IdxDB.prototype.open = function () {
let self = this;
navigator.storage && navigator.storage.persist();
return this.db || new Promise(function (resolve, reject) {
const req = IndexedDB.open(self.id + (self.field ? ":" + self.field : ""), VERSION);
req.onupgradeneeded = function () {
const db = self.db = this.result;
// Using Indexes + IDBKeyRange on schema map => [key, res, id] performs
// too bad and blows up amazingly in size
// The schema map:key => [res][id] is currently used instead
// In fact that bypass the idea of a storage solution,
// IndexedDB is such a poor contribution :(
fields.forEach(ref => {
db.objectStoreNames.contains(ref) || db.createObjectStore(ref); //{ autoIncrement: true /*keyPath: "id"*/ }
//.createIndex("idx", "ids", { multiEntry: true, unique: false });
});
// switch(event.oldVersion){ // existing db version
// case 0:
// // version 0 means that the client had no database
// // perform initialization
// case 1:
// // client had version 1
// // update
// }
};
req.onblocked = function (event) {
// this event shouldn't trigger if we handle onversionchange correctly
// it means that there's another open connection to the same database
// and it wasn't closed after db.onversionchange triggered for it
console.error("blocked", event);
reject();
};
req.onerror = function (event) {
console.error(this.error, event);
reject();
};
req.onsuccess = function () {
self.db = this.result; //event.target.result;
self.db.onversionchange = function () {
//database is outdated
self.close();
};
resolve(self);
};
});
};
IdxDB.prototype.close = function () {
this.db.close();
this.db = null;
};
/**
* @return {!Promise<undefined>}
*/
IdxDB.prototype.destroy = function () {
const req = IndexedDB.deleteDatabase(this.id + (this.field ? ":" + this.field : ""));
return promisfy(req);
};
// IdxDB.prototype.set = function(ref, key, ctx, data){
// const transaction = this.db.transaction(ref, "readwrite");
// const map = transaction.objectStore(ref);
// const req = map.put(data, ctx ? ctx + ":" + key : key);
// return transaction;//promisfy(req, callback);
// };
// IdxDB.prototype.delete = function(ref, key, ctx){
// const transaction = this.db.transaction(ref, "readwrite");
// const map = transaction.objectStore(ref);
// const req = map.delete(ctx ? ctx + ":" + key : key);
// return transaction;//promisfy(req, callback);
// };
/**
* @return {!Promise<undefined>}
*/
IdxDB.prototype.clear = function () {
const transaction = this.db.transaction(fields, "readwrite");
for (let i = 0; i < fields.length; i++) {
transaction.objectStore(fields[i]).clear();
}
return promisfy(transaction);
};
/**
* @param {!string} key
* @param {string=} ctx
* @param {number=} limit
* @param {number=} offset
* @param {boolean=} resolve
* @param {boolean=} enrich
* @return {!Promise<SearchResults|EnrichedSearchResults>}
*/
IdxDB.prototype.get = function (key, ctx, limit = 0, offset = 0, resolve = /* tag? */!0, enrich = !1) {
const transaction = this.db.transaction(ctx ? "ctx" : "map", "readonly"),
map = transaction.objectStore(ctx ? "ctx" : "map"),
req = map.get(ctx ? ctx + ":" + key : key),
self = this;
return promisfy(req).then(function (res) {
let result = [];
if (!res || !res.length) return result;
if (resolve) {
if (!limit && !offset && 1 === res.length) {
return res[0];
}
for (let i = 0, arr; i < res.length; i++) {
if ((arr = res[i]) && arr.length) {
if (offset >= arr.length) {
offset -= arr.length;
continue;
}
const end = limit ? offset + Math.min(arr.length - offset, limit) : arr.length;
for (let j = offset; j < end; j++) {
result.push(arr[j]);
}
offset = 0;
if (result.length === limit) {
break;
}
}
}
return enrich ? self.enrich(result) : result;
} else {
return res;
}
});
};
/**
* @param {!string} tag
* @param {number=} limit
* @param {number=} offset
* @param {boolean=} enrich
* @return {!Promise<SearchResults|EnrichedSearchResults>}
*/
IdxDB.prototype.tag = function (tag, limit = 0, offset = 0, enrich = !1) {
const transaction = this.db.transaction("tag", "readonly"),
map = transaction.objectStore("tag"),
req = map.get(tag),
self = this;
return promisfy(req).then(function (ids) {
if (!ids || !ids.length || offset >= ids.length) return [];
if (!limit && !offset) return ids;
const result = ids.slice(offset, offset + limit);
return enrich ? self.enrich(result) : result;
});
};
/**
* @param {SearchResults} ids
* @return {!Promise<EnrichedSearchResults>}
*/
IdxDB.prototype.enrich = function (ids) {
if ("object" != typeof ids) {
ids = [ids];
}
const transaction = this.db.transaction("reg", "readonly"),
map = transaction.objectStore("reg"),
promises = [];
for (let i = 0; i < ids.length; i++) {
promises[i] = promisfy(map.get(ids[i]));
}
return Promise.all(promises).then(function (docs) {
for (let i = 0; i < docs.length; i++) {
docs[i] = {
id: ids[i],
doc: docs[i] ? JSON.parse(docs[i]) : null
};
}
return docs;
});
};
/**
* @param {number|string} id
* @return {!Promise<undefined>}
*/
IdxDB.prototype.has = function (id) {
const transaction = this.db.transaction("reg", "readonly"),
map = transaction.objectStore("reg"),
req = map.getKey(id);
return promisfy(req);
};
IdxDB.prototype.search = null;
// IdxDB.prototype.has = function(ref, key, ctx){
// const transaction = this.db.transaction(ref, "readonly");
// const map = transaction.objectStore(ref);
// const req = map.getKey(ctx ? ctx + ":" + key : key);
// return promisfy(req);
// };
IdxDB.prototype.info = function () {
// todo
};
/**
* @param {!string} ref
* @param {!string} modifier
* @param {!Function} task
*/
IdxDB.prototype.transaction = function (ref, modifier, task) {
let store = this.trx[ref + ":" + modifier];
if (store) return task.call(this, store);
let transaction = this.db.transaction(ref, modifier);
this.trx[ref + ":" + modifier] = store = transaction.objectStore(ref);
return new Promise((resolve, reject) => {
transaction.onerror = err => {
transaction.abort();
transaction = store = null;
reject(err);
//db.close;
};
transaction.oncomplete = res => {
transaction = store = null;
resolve(res || !0);
//db.close;
};
const promise = task.call(this, store);
//
this.trx[ref + ":" + modifier] = null;
return promise;
});
};
IdxDB.prototype.commit = async function (flexsearch, _replace, _append) {
// process cleanup tasks
if (_replace) {
await this.clear();
// there are just removals in the task queue
flexsearch.commit_task = [];
} else {
let tasks = flexsearch.commit_task;
flexsearch.commit_task = [];
for (let i = 0, task; i < tasks.length; i++) {
task = tasks[i];
// there are just removals in the task queue
if (task.clear) {
await this.clear();
_replace = !0;
break;
} else {
tasks[i] = task.del;
}
}
if (!_replace) {
if (!_append) {
tasks = tasks.concat(toArray(flexsearch.reg));
}
tasks.length && (await this.remove(tasks));
}
}
if (!flexsearch.reg.size) {
return;
}
await this.transaction("map", "readwrite", function (store) {
for (const item of flexsearch.map) {
const key = item[0],
value = item[1];
if (!value.length) continue;
if (_replace) {
store.put(value, key);
continue;
}
store.get(key).onsuccess = function () {
let result = this.result,
changed;
if (result && result.length) {
const maxlen = Math.max(result.length, value.length);
for (let i = 0, res, val; i < maxlen; i++) {
val = value[i];
if (val && val.length) {
res = result[i];
if (res && res.length) {
for (let j = 0; j < val.length; j++) {
res.push(val[j]);
}
changed = 1;
//result[i] = res.concat(val);
//result[i] = new Set([...result[i], ...value[i]]);
//result[i] = result[i].union(new Set(value[i]));
} else {
result[i] = val;
changed = 1;
//result[i] = new Set(value[i])
}
}
}
} else {
result = value;
changed = 1;
//result = [];
//for(let i = 0; i < value.length; i++){
// if(value[i]) result[i] = new Set(value[i]);
//}
}
changed && store.put(result, key);
};
}
});
await this.transaction("ctx", "readwrite", function (store) {
for (const ctx of flexsearch.ctx) {
const ctx_key = ctx[0],
ctx_value = ctx[1];
for (const item of ctx_value) {
const key = item[0],
value = item[1];
if (!value.length) continue;
if (_replace) {
store.put(value, ctx_key + ":" + key);
continue;
}
store.get(ctx_key + ":" + key).onsuccess = function () {
let result = this.result,
changed;
if (result && result.length) {
const maxlen = Math.max(result.length, value.length);
for (let i = 0, res, val; i < maxlen; i++) {
val = value[i];
if (val && val.length) {
res = result[i];
if (res && res.length) {
for (let j = 0; j < val.length; j++) {
res.push(val[j]);
}
//result[i] = res.concat(val);
changed = 1;
} else {
result[i] = val;
changed = 1;
}
}
}
} else {
result = value;
changed = 1;
}
changed && store.put(result, ctx_key + ":" + key);
};
}
}
});
if (flexsearch.store) {
await this.transaction("reg", "readwrite", function (store) {
for (const item of flexsearch.store) {
const id = item[0],
doc = item[1];
store.put("object" == typeof doc ? JSON.stringify(doc) : 1, id);
}
});
} else if (!flexsearch.bypass) {
await this.transaction("reg", "readwrite", function (store) {
for (const id of flexsearch.reg.keys()) {
store.put(1, id);
}
});
}
if (flexsearch.tag) {
await this.transaction("tag", "readwrite", function (store) {
for (const item of flexsearch.tag) {
const tag = item[0],
ids = item[1];
if (!ids.length) continue;
store.get(tag).onsuccess = function () {
let result = this.result;
result = result && result.length ? result.concat(ids) : ids;
store.put(result, tag);
};
}
});
}
// TODO
// await this.transaction("cfg", "readwrite", function(store){
// store.put({
// "charset": flexsearch.charset,
// "tokenize": flexsearch.tokenize,
// "resolution": flexsearch.resolution,
// "fastupdate": flexsearch.fastupdate,
// "compress": flexsearch.compress,
// "encoder": {
// "minlength": flexsearch.encoder.minlength
// },
// "context": {
// "depth": flexsearch.depth,
// "bidirectional": flexsearch.bidirectional,
// "resolution": flexsearch.resolution_ctx
// }
// }, "current");
// });
flexsearch.map.clear();
flexsearch.ctx.clear();
flexsearch.tag && flexsearch.tag.clear();
flexsearch.store && flexsearch.store.clear();
flexsearch.document || flexsearch.reg.clear();
};
/**
* @param {IDBCursorWithValue} cursor
* @param {Array<number|string>} ids
* @param {boolean=} _tag
*/
function handle(cursor, ids, _tag) {
const arr = cursor.value;
let changed,
parse,
count = 0;
for (let x = 0, result; x < arr.length; x++) {
// tags has no resolution layer
if (result = _tag ? arr : arr[x]) {
for (let i = 0, pos, id; i < ids.length; i++) {
id = ids[i];
pos = result.indexOf(parse ? parseInt(id, 10) : id);
if (0 > pos && !parse && "string" == typeof id && !isNaN(id)) {
pos = result.indexOf(parseInt(id, 10));
pos && (parse = 1);
}
if (0 <= pos) {
changed = 1;
if (1 < result.length) {
result.splice(pos, 1);
} else {
arr[x] = [];
break;
}
}
}
count += result.length;
}
if (_tag) break;
}
if (!count) {
cursor.delete();
//store.delete(cursor.key);
} else if (changed) {
//await new Promise(resolve => {
cursor.update(arr); //.onsuccess = resolve;
//});
}
cursor.continue();
}
/**
* @param {Array<number|string>} ids
* @return {!Promise<undefined>}
*/
IdxDB.prototype.remove = function (ids) {
if ("object" != typeof ids) {
ids = [ids];
}
return (/** @type {!Promise<undefined>} */Promise.all([this.transaction("map", "readwrite", function (store) {
store.openCursor().onsuccess = function () {
const cursor = this.result;
cursor && handle(cursor, ids);
};
}), this.transaction("ctx", "readwrite", function (store) {
store.openCursor().onsuccess = function () {
const cursor = this.result;
cursor && handle(cursor, ids);
};
}), this.transaction("tag", "readwrite", function (store) {
store.openCursor().onsuccess = function () {
const cursor = this.result;
cursor && handle(cursor, ids, !0);
};
}),
// let filtered = [];
this.transaction("reg", "readwrite", function (store) {
for (let i = 0; i < ids.length; i++) {
store.delete(ids[i]);
}
// return new Promise(resolve => {
// store.openCursor().onsuccess = function(){
// const cursor = this.result;
// if(cursor){
// const id = cursor.value;
// if(ids.includes(id)){
// filtered.push(id);
// cursor.delete();
// }
// cursor.continue();
// }
// else{
// resolve();
// }
// };
// });
})
// ids = filtered;
])
);
};
/**
* @param {IDBRequest} req
* @param {Function=} callback
* @return {!Promise<undefined>}
*/
function promisfy(req, callback) {
return new Promise((resolve, reject) => {
/** @this {IDBRequest} */
req.onsuccess = function () {
callback && callback(this.result);
resolve(this.result);
};
/** @this {IDBRequest} */
req.oncomplete = function () {
callback && callback(this.result);
resolve(this.result);
};
req.onerror = reject;
req = null;
});
}