Skip to content

Instantly share code, notes, and snippets.

@shakogegia
Forked from yairopro/Storage.js
Created July 10, 2018 12:20
Show Gist options
  • Save shakogegia/a13da4d73656f096f439cda5d90ac3a3 to your computer and use it in GitHub Desktop.
Save shakogegia/a13da4d73656f096f439cda5d90ac3a3 to your computer and use it in GitHub Desktop.
Modules to store in DB or as key-value for react-native.
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