lib/IndexedFTS.js
- import IFTSTransaction from './Transaction';
- import IFTSSchema from './Schema';
-
-
- /**
- * The database of IndexedFTS.
- *
- * Almost methods are the same interface as {@link IDBTransaction} and {@link IFTSArrayPromise}.
- */
- export default class IndexedFTS {
- /**
- * Create or open IndexedFTS.
- *
- * Database has name and schema's version.
- * The name is a name of the database in the storage.
- *
- * The schema is an object that key is column name and value is a definition of indexes. Schema can't change in same version database.
- * If you want change schema of database, please change version number.
- * Please be careful, all contents will remove when changing the version number.
- *
- * Index types are 'primary', 'unique', 'fulltext', 'ngram', 'word', or 'normal'.
- *
- * 'primary' is a primary key of the database. 'primary' can't set to multiple columns.
- * 'unique' is columns that have a unique value in the database.
- * The 'normal' will enable when not primary and not unique.
- * 'primary', 'unique' and 'normal' column can numeric search (eg. {@link IndexedFTS#lower} or {@link IndexedFTS#between}).
- *
- * If set 'ngram' IndexedFTS will make 2-gram index table for full-text search.
- * 'fulltext' is alias to 'ngram'.
- *
- * 'word' is word based index.
- * The word index will split text with whitespaces and store those.
- * Word index is faster than the 'ngram' index but can't find a partial match in the word.
- *
- * If you want to set some index types, please use object like `{unique: true, fulltext: true, normal: false}`.
- *
- * @param {string} name - name of new (or open) database.
- * @param {number} version - schema's version of database.
- * @param {object|IFTSSchema} schema - database schema.
- * @param {object} [options] - other options.
- * @param {string} [options.index_prefix='indexedfts_'] - prefix of indexes for full-text search.
- * @param {object} [options.scope=window] - endpoints for IndexedDB API.
- *
- * @throws {InvalidSchemaError}
- */
- constructor(name, version, schema, options={}) {
- /** @type {string} */
- this.index_prefix = options.index_prefix || 'indexedfts_';
-
- /** @type {object} */
- this.scope = options.scope || window;
-
- /** @type {string} */
- this.name = name;
-
- /** @type {number} */
- this.version = version;
-
- /** @type {IFTSSchema} */
- this.schema = schema instanceof IFTSSchema ? schema : new IFTSSchema(schema);
-
-
- /** @type {IDBDatabase} */
- this.db = null;
- }
-
- /**
- * Delete database.
- *
- * Must be close all IndexedFTS before delete database.
- *
- * @param {string} name - name of target database. this method will success even if no such database.
- * @param {object} [scope] - endpoints for IndexedDB API.
- *
- * @return {Promise<undefined>}
- */
- static delete(name, scope=null) {
- return new Promise((resolve, reject) => {
- const req = (scope || window).indexedDB.deleteDatabase(name);
- req.onsuccess = ev => resolve();
- req.onerror = ev => reject(ev);
- });
- }
-
- /**
- * Open database.
- *
- * @return {Promise<undefined>}
- */
- open() {
- return new Promise((resolve, reject) => {
- const request = this.scope.indexedDB.open(this.name, this.version);
-
- request.onsuccess = ev => {
- this.db = ev.target.result;
- resolve(this);
- };
- request.onerror = reject;
-
- request.onupgradeneeded = ev => {
- this.db = ev.target.result;
-
- const store = this.db.createObjectStore('data', this.schema._storeOption);
-
- store.onerror = reject;
-
- this.schema.uniqueIndexes.forEach(x => store.createIndex(x, x, {unique: true}));
-
- this.schema.normalIndexes.forEach(x => store.createIndex(x, x, {unique: false}));
-
- this.schema.ngramIndexes.forEach(column => {
- const fts_store = this.db.createObjectStore(this.index_prefix + 'ngram_' + column, {autoIncrement: true});
- fts_store.onerror = reject
- fts_store.createIndex('key', 'key', {unique: false});
- fts_store.createIndex('token', 'token', {unique: false});
- fts_store.createIndex('lower', 'lower', {unique: false});
- });
-
- this.schema.wordIndexes.forEach(column => {
- const fts_store = this.db.createObjectStore(this.index_prefix + 'word_' + column, {autoIncrement: true});
- fts_store.onerror = reject
- fts_store.createIndex('key', 'key', {unique: false});
- fts_store.createIndex('word', 'word', {unique: false});
- fts_store.createIndex('lower', 'lower', {unique: false});
- });
- };
- });
- }
-
- /**
- * Close database.
- */
- close() {
- this.db.close();
- }
-
- /**
- * Make new {@link IFTSTransaction}.
- *
- * @param {"readonly"|"readwrite"} mode - mode of transaction.
- * @param {string[]|null} target - open index targets. open for all if null.
- *
- * @return {IFTSTransaction}
- */
- transaction(mode='readonly', target=null) {
- if (target === null) {
- const ngrams = [...this.schema.ngramIndexes].map(x => this.index_prefix + 'ngram_' + x);
- const words = [...this.schema.wordIndexes].map(x => this.index_prefix + 'word_' + x);
- target = ngrams.concat(words).concat(['data']);
- }
- return new IFTSTransaction(this, this.db.transaction(target, mode));
- }
-
- /**
- * Put contents into database.
- *
- * @param {object} contents - contents for save. allowed multiple arguments.
- *
- * @return {Promise<IndexedFTS>} returns self for chain.
- */
- put(...contents) {
- return this.transaction('readwrite').put(...contents).then(() => this);
- }
-
- /**
- * Delete contents from database.
- *
- * @param {object} keys - key of contents.
- *
- * @return {Promise<IndexedFTS>} returns self for chain. Will reject with {@link InvalidKeyError} if keys included null or undefined.
- */
- delete(...keys) {
- return this.transaction('readwrite').delete(...keys).then(() => this);
- }
-
- /**
- * Get content by primary key.
- *
- * @param {object} key - the key of content.
- *
- * @return {Promise<object|undefined>} content. promise will reject with {@link InvalidKeyError} if keys included null or undefined. result value will be undefined if not found.
- */
- get(key) {
- return this.transaction('readonly', 'data').get(key);
- }
-
- /**
- * Get filtered contents.
- *
- * @ignore
- */
- _getFiltered(fun) {
- return fun(this.transaction('readonly', 'data'));
- }
-
- /**
- * Get all contents.
- *
- * @return {IFTSArrayPromise} contents.
- */
- getAll() {
- return this._getFiltered(x => x.getAll());
- }
-
- /**
- * Do something process for each elements and returns {@link IFTSArrayPromise}.
- *
- * NOTE: This method doesn't fast. May better do filtering before doing map if need filtering.
- *
- * @param {function(content: object, index: Number): object} fun - function for processing element.
- *
- * @return {IFTSArrayPromise}
- */
- map(fun) {
- return this._getFiltered(x => x.map(fun));
- }
-
- /**
- * Filtering elements by function and returns {@link IFTSArrayPromise}.
- *
- * WARNING: This method won't use the index. Other methods(eg. {@link IFTSTransaction#equals or @link IFTSTransaction#lower} may faster than this.
- *
- * @param {function(content: object, index: Number): object} fun - function for filtering element.
- *
- * @return {IFTSArrayPromise}
- */
- filter(fun) {
- return this._getFiltered(x => x.filter(fun));
- }
-
- /**
- * Sort and get all contents.
- *
- * @param {object} column - the column for sorting.
- * @param {'asc'|'desc'} [order='asc'] - sort order.
- * @param {Number} [offset=0] - starting offset of the result.
- * @param {Number} [limit] - maximum number of result length. will unlimited if omitted.
- *
- * @return {IFTSArrayPromise} sorted contents.
- */
- sort(column, order='asc', offset=0, limit=undefined) {
- return this._getFiltered(x => x.sort(column, order, offset, limit));
- }
-
- /**
- * Get contents that have fully matched property.
- *
- * @param {object} column - column name for search.
- * @param {object} value - value for search.
- *
- * @return {IFTSArrayPromise} matched contents. may reject with {@link NoSuchColumnError}.
- */
- equals(column, value) {
- return this._getFiltered(x => x.equals(column, value));
- }
-
- /**
- * Get contents that have property lower than value.
- *
- * @param {object} column - column name for search.
- * @param {object} value - value for search.
- *
- * @return {IFTSArrayPromise} matched contents. may reject with {@link NoSuchColumnError}.
- */
- lower(column, value) {
- return this._getFiltered(x => x.lower(column, value));
- }
-
- /**
- * Get contents that have property greater than value.
- *
- * @param {object} column - column name for search.
- * @param {object} value - value for search.
- *
- * @return {IFTSArrayPromise} matched contents. may reject with {@link NoSuchColumnError}.
- */
- greater(column, value) {
- return this._getFiltered(x => x.greater(column, value));
- }
-
- /**
- * Get contents that have property lower than value or equals value.
- *
- * @param {object} column - column name for search.
- * @param {object} value - value for search.
- *
- * @return {IFTSArrayPromise} matched contents. may reject with {@link NoSuchColumnError}.
- */
- lowerOrEquals(column, value) {
- return this._getFiltered(x => x.lowerOrEquals(column, value));
- }
-
- /**
- * Get contents that have property greater than value or equals value.
- *
- * @param {object} column - column name for search.
- * @param {object} value - value for search.
- *
- * @return {IFTSArrayPromise} matched contents. may reject with {@link NoSuchColumnError}.
- */
- greaterOrEquals(column, value) {
- return this._getFiltered(x => x.greaterOrEquals(column, value));
- }
-
- /**
- * Get contents that have property is between argument values.
- *
- * @param {object} column - column name for search.
- * @param {object} lower - minimal value.
- * @param {object} upper - maximum value.
- *
- * @return {IFTSArrayPromise} matched contents. may reject with {@link NoSuchColumnError}.
- */
- between(column, lower, upper) {
- return this._getFiltered(x => x.between(column, lower, upper));
- }
-
- /**
- * Get contents that have matched property by full-text search.
- *
- * All target columns have to made ngram index when created database.
- * If you didn't made ngram index, you can use {@link IFTSArrayPromise#search} (but this way is very slow).
- *
- *
- * @param {object|object[]} columns - column names for search.
- * @param {string} query - query for search.
- * @param {object} [options] - optional arguments.
- * @param {boolean} [options.ignoreCase=false] - ignore case if true. default is false.
- *
- * @return {IFTSArrayPromise} matched contents. may reject with {@link NoSuchColumnError}.
- */
- search(columns, query, options={}) {
- return this.transaction().search(columns, query, options);
- }
-
- /**
- * Find contents that have fully matched word in property.
- *
- * All target columns have to made word index when created database.
- * If you didn't made word index, you can use {@link IFTSArrayPromise#searchWord} (but this way is very slow).
- *
- *
- * @param {object|object[]} columns - column names for search.
- * @param {string} query - query for search.
- * @param {object} [options] - optional arguments.
- * @param {boolean} [options.ignoreCase=false] - ignore case if true. default is false.
- *
- * @return {IFTSArrayPromise} matched contents. may reject with {@link NoSuchColumnError}.
- */
- searchWord(columns, query, options={}) {
- return this.transaction().searchWord(columns, query, options);
- }
-
- /**
- * Get N-Gram set from index.
- *
- * @param {string} column - name of column.
- * @param {object} [options] - optional arguments.
- * @param {boolean} [options.ignoreCase=false] - ignore case when make result.
- *
- * @return {Promise<Map<string, number>>}
- */
- getNGrams(column, options={}) {
- return this.transaction().getNGrams(column, options);
- }
-
- /**
- * Get word set from index.
- *
- * @param {string} column - name of column.
- * @param {object} [options] - optional arguments.
- * @param {boolean} [options.ignoreCase=false] - ignore case when make result.
- *
- * @return {Promise<Map<string, number>>}
- */
- getWords(column, options={}) {
- return this.transaction().getWords(column, options);
- }
- }