Last active
July 10, 2018 12:20
-
-
Save yairopro/2a46ef6922e3009b1fc7b94e0613b3bf to your computer and use it in GitHub Desktop.
Modules to store in DB or as key-value for react-native.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SQLite from 'react-native-sqlite-2' | |
import {Platform} from "react-native"; | |
const openedDb = {}; | |
/** | |
* Wrapper for react-native-sqlite-2. | |
* Methods: | |
* <ul> | |
* <li>Promise<Array<Object>> {@link execute}(statement, ...variables) : Execute a statement. Variables will be binded into the statement. It returns the list of items matching the statement.</li> | |
* <li>Promise<Array<Object>> {@link executeAdvanced}(statement, ...variables) : Same as {@link execute}, except that the returned value is {@link Database.Result}.</li> | |
* </ul> | |
*/ | |
class Database { | |
constructor(name){ | |
// check opened db | |
if (!openedDb[name]) | |
openedDb[name] = SQLite.openDatabase(name + '.db', '1.0', '', 1); | |
this._database = openedDb[name]; | |
} | |
/** | |
* @param {String} statement Statement to execute. | |
* @param {Array.<String|Number|Boolean>} variables Variables to bind into the statement. | |
* @returns {Promise.<Array.<Object>>} The array of all items matching the statement. | |
*/ | |
execute(statement, ...variables){ | |
return this.executeAdvanced(statement, ...variables) | |
// return loaded items directly | |
.then(response => response.rows); | |
} | |
/** | |
* Same as {@link execute}, but returns a result with more details. | |
* @param {String} statement Statement to execute. | |
* @param {Array.<String|Number|Boolean>} variables Variables to bind into the statement. | |
* @returns {Promise.<Database.Result>} The statement's result. | |
*/ | |
executeAdvanced(statement, ...variables){ | |
return new Promise((resolve, reject) => | |
this._database.transaction(transaction => | |
transaction.executeSql(statement, variables, | |
(_, result) => { | |
// convert | |
result.rows = result.rows._array; | |
// return the entire result | |
resolve(result); | |
}, | |
(_, error) => reject(error)) | |
) | |
) | |
} | |
/** | |
* @returns {Promise.<Array.<String>>} All tables' names. | |
*/ | |
getTables(){ | |
return execute('SELECT name FROM sqlite_master WHERE type = "table"') | |
.then(results => results.map(result => result.name)) | |
.then(names => Platform.select({ | |
// remove android meta data table | |
android : names.filter(name => name != 'android_metadata'), | |
default : names | |
})); | |
} | |
} | |
/** | |
* Module to store key-value data. You must pass a string namespace to its constructor. | |
*/ | |
class KeyValueStorage { | |
constructor(namespace){ | |
this.namespace = namespace; | |
this.init = database.execute(Queries.init(namespace)) | |
// log if error | |
.catch(error => { | |
console.warn("Impossible to init the database for KeyValueStorage with namespace:", namespace, error); | |
throw error; | |
}); | |
} | |
/** | |
* Set a map og key values. | |
* @param {Object} map Map of key-value to store. | |
* @returns {Promise<Object.<Error>>} Map of possible errors for each key. | |
*/ | |
async set(map) { | |
await this.init; | |
let promises = {}; | |
Object.keys(map) | |
.forEach(key => { | |
let value = map[key]; | |
// serialize value | |
value = JSON.stringify(value); | |
// build statement | |
let {statement, variables} = Queries.set(this.namespace, key, value); | |
// persist | |
promises[key] = database.execute(statement, ...variables); | |
}); | |
// await for each to resolve, and catch their errors for each of them | |
let errors = {}; | |
for (let key of Object.keys(promises)) | |
try { | |
await promises[key]; | |
} catch (error) { | |
errors[key] = error; | |
} | |
return errors; | |
} | |
/** | |
* Load values for given keys. | |
* @param {Array } keys Keys of value to load. | |
* @returns {Promise.<Object.<{value, error}>>} A map of object {value, error}. If no error is in the key, then you can get the value. | |
*/ | |
async get(...keys) { | |
await this.init; | |
// load | |
let {statement, variables} = Queries.get(this.namespace, keys); | |
let items = await database.execute(statement, ...variables); | |
// convert into one item | |
let results = {}; | |
items.forEach(item => { | |
let value = JSON.parse(item.value); | |
// wrap each value inside an object, without error field | |
results[item.key] = {value}; | |
}); | |
// fill all unset keys with empty object (no error, and no value) | |
keys.forEach(key => { | |
results[key] = results[key] || {}; | |
}); | |
return results; | |
} | |
/** | |
* Remove values for given keys. | |
* @param keys Keys to remove. | |
* @returns {Promise<Object.<Error>>} Map of possible errors for each key. | |
*/ | |
async remove(...keys) { | |
await this.init; | |
let {statement, variables} = Queries.remove(this.namespace, keys); | |
await database.execute(statement, ...variables); | |
// return no errors | |
return {}; | |
} | |
/** | |
* @returns {Promise<Array.<String>>} All keys stored. | |
*/ | |
async getKeys(){ | |
await this.init; | |
let items = await database.execute(`SELECT ${Columns.KEY} from ${this.namespace}`); | |
return items.map(item => item.key); | |
} | |
/** | |
* @returns {Promise<Array<String>>} All namespaces stored. | |
*/ | |
static getNamespaces(){ | |
return database.getTables(); | |
} | |
} | |
export {Database, KeyValueStorage} | |
// --------- private --------- | |
const database = new Database("KeyValues"); | |
const Columns = { | |
KEY : "key", | |
VALUE : "value", | |
}; | |
const Queries = { | |
// init statement | |
init : table => `CREATE TABLE IF NOT EXISTS ${table}(${Columns.KEY} VARCHAR(1000000000) PRIMARY KEY NOT NULL UNIQUE, ${Columns.VALUE} VARCHAR(1000000000))`, | |
set : (table, key, value) => ({ | |
statement : `INSERT OR REPLACE INTO ${table} (${Columns.KEY}, ${Columns.VALUE}) values (?, ?)`, | |
variables : [key, value] | |
}), | |
get : (table, keys) => ({ | |
// place placeholders | |
statement : `SELECT * FROM ${table} WHERE ${Columns.KEY} IN (${keys.map(key => '?').join(',')})`, | |
// convert keys into strings | |
variables : keys.map(key => key+""), | |
}), | |
remove : (table, keys) => ({ | |
// place placeholders | |
statement : `DELETE FROM ${table} WHERE ${Columns.KEY} IN (${keys.map(key => '?').join(',')})`, | |
// convert keys into strings | |
variables : keys.map(key => key+""), | |
}), | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment