├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.[aod] 2 | *.DS_Store 3 | .DS_Store 4 | *Thumbs.db 5 | *.iml 6 | .gradle 7 | .idea 8 | node_modules 9 | npm-debug.log 10 | /android/build 11 | /ios/**/*xcuserdata* 12 | /ios/**/*xcshareddata* 13 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | .DS_Store 3 | *Thumbs.db 4 | .gradle 5 | .idea 6 | *.iml 7 | npm-debug.log 8 | node_modules 9 | /android/build 10 | /ios/**/*xcuserdata* 11 | /ios/**/*xcshareddata* 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2015-2016 YunJiang.Fang <42550564@qq.com> 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Native mongoose (remobile) 2 | A AsyncStorage based mongoose like storage for react-native 3 | 4 | ## Installation 5 | ```sh 6 | npm install @remobile/react-native-mongoose --save 7 | ``` 8 | 9 | ## Usage 10 | 11 | ### Example 12 | ```js 13 | 'use strict'; 14 | 15 | var React = require('react'); 16 | var ReactNative = require('react-native'); 17 | var { 18 | StyleSheet, 19 | View, 20 | Image 21 | } = ReactNative; 22 | var { 23 | StyleSheet, 24 | View, 25 | AsyncStorage, 26 | } = ReactNative; 27 | 28 | 29 | var Button = require('@remobile/react-native-simple-button'); 30 | var Mongoose = require('react-native-mongoose'); 31 | 32 | const DB_NAME = "fang"; 33 | const CLT_NAME = "number"; 34 | module.exports = React.createClass({ 35 | componentDidMount() { 36 | this.db = new Mongoose(DB_NAME); 37 | this.collection = this.db.collection(CLT_NAME, {max:30, unique:['a']}); 38 | }, 39 | doClear() { 40 | AsyncStorage.removeItem(DB_NAME); 41 | }, 42 | doShowList() { 43 | (async function(){ 44 | var list = await AsyncStorage.getItem(DB_NAME); 45 | console.log('result:', JSON.parse(list)); 46 | 47 | })(); 48 | }, 49 | doShowKeys() { 50 | (async function(){ 51 | var list = await AsyncStorage.getAllKeys(); 52 | console.log('result:', list); 53 | })(); 54 | }, 55 | async doInsert() { 56 | var info = { 57 | a : 4, 58 | b : 3, 59 | }; 60 | var collection = this.collection; 61 | var list = await collection.insert(info).catch(error=>console.log(error));; 62 | console.log("list"); 63 | console.log(list); 64 | }, 65 | async doFind() { 66 | var collection = this.collection; 67 | var req = await collection.find(); 68 | console.log(req); 69 | }, 70 | async doFindOne() { 71 | var collection = this.collection; 72 | var req = await collection.findOne({b:3,a:1}); 73 | console.log(req); 74 | }, 75 | async doRemove() { 76 | var collection = this.collection; 77 | var req = await collection.remove(); 78 | console.log(req); 79 | var req = await collection.find(); 80 | console.log(req); 81 | }, 82 | async doUpsert() { 83 | var collection = this.collection; 84 | var info = { 85 | a : 4, 86 | b : 6, 87 | }; 88 | var list = await collection.upsert(info, {a:4}).catch(error=>console.log(error));; 89 | console.log("list"); 90 | console.log(list); 91 | }, 92 | render() { 93 | return ( 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | ); 105 | } 106 | }); 107 | 108 | 109 | var styles = StyleSheet.create({ 110 | container: { 111 | flex: 1, 112 | backgroundColor: 'transparent', 113 | justifyContent: 'space-around', 114 | paddingVertical: 150, 115 | }, 116 | }); 117 | ``` 118 | 119 | ### method 120 | * #### creat dataBase 121 | ```js 122 | var db = new Mongoose(dbname); 123 | ``` 124 | * dbname: the name of database, in AsyncStorage it is a item key 125 | 126 | * #### clear memory 127 | ```js 128 | db.clear(); 129 | ``` 130 | * react-native-mongoose use memory chache database, when not use it, use it clear memory; 131 | 132 | 133 | * #### creat collection 134 | ```js 135 | var collection = this.db.collection(collectionName, capped); 136 | ``` 137 | * collectionName: the name of collection 138 | * capped: {max: Number, unique:String|Array} 139 | * max: max rows in collection, if not set, have no limit 140 | * unique: set unique primary key, it can be a single String or a array for keys 141 | 142 | * #### insert 143 | ```js 144 | var doc = collection.insert(docs); 145 | ``` 146 | * docs: to be insert docs, if set capped.max, when reach capped.max, will be replace oldest one, if set capped.unique, e.g: capped.unique is 'a', then a is unique. 147 | 148 | 149 | * #### upsert 150 | ```js 151 | var doc = collection.upsert(docs, query, params); 152 | ``` 153 | * docs: need insert or update data 154 | * query: look Query help 155 | * params: {limit:Number, offset:Number, strict:bool}; 156 | * limit: need upsert number 157 | * offset: need upsert start position 158 | * strict: set compare strict mode, look Query help 159 | 160 | 161 | * #### update 162 | ```js 163 | var doc = collection.upsert(docs, query, params); 164 | ``` 165 | * docs: need update data 166 | * query: look Query help 167 | * params: {limit:Number, offset:Number, strict:bool}; 168 | * limit: need update number 169 | * offset: need update start position 170 | * strict: set compare strict mode, look Query help 171 | 172 | 173 | * #### remove 174 | ```js 175 | var doc = collection.remove(query, params); 176 | ``` 177 | * query: look Query help 178 | * params: {limit:Number, offset:Number, strict:bool}; 179 | * limit: need remove number 180 | * offset: need remove start position 181 | * strict: set compare strict mode, look Query help 182 | 183 | 184 | * #### find 185 | ```js 186 | var docs = collection.find(query, params); 187 | ``` 188 | * query: look Query help 189 | * params: {limit:Number, offset:Number, strict:bool}; 190 | * limit: need find number 191 | * offset: need find start position 192 | * strict: set compare strict mode, look Query help 193 | 194 | 195 | * #### findOne 196 | ```js 197 | var doc = collection.findOne(query); 198 | ``` 199 | * query: look Query help 200 | * params: {limit:Number, offset:Number, strict:bool}; 201 | * limit: 1 202 | * offset: need findOne start position 203 | * strict: set compare strict mode, look Query help 204 | 205 | ### Query help 206 | Query can be a object like {a:1, b:2}, or {a:{$eq:1}, b:{$eq:2}} 207 | also can be a function lick {a:function(a){return a>1}} 208 | operand like follows: 209 | * '$gt': 210 | ```js 211 | return val1 > val2; 212 | ``` 213 | * '$lt': 214 | ```js 215 | return val1 < val2; 216 | ``` 217 | * '$gte': 218 | ```js 219 | return val1 >= val2; 220 | ``` 221 | * '$lte': 222 | ```js 223 | return val1 <= val2; 224 | ``` 225 | * '$ne': 226 | ```js 227 | return strict ? val1!==val2 : val1!=val2; 228 | ``` 229 | * '$eq': 230 | ```js 231 | return strict ? val1===val2 : val1==val2; 232 | ``` 233 | * '$like': 234 | ```js 235 | return new RegExp(val2).test(val1); 236 | ``` 237 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | let ReactNative = require('react-native'); 3 | let { 4 | AsyncStorage, 5 | } = ReactNative; 6 | 7 | class Collection { 8 | constructor (collectionName, dbName, capped, memory) { 9 | this.collectionName = collectionName; 10 | this.dbName = dbName; 11 | this.capped = capped || {}; 12 | this.strict = true; 13 | this.memory = memory; 14 | } 15 | checkMatch (item, query, strict) { 16 | let match = true; 17 | let self = this; 18 | query = this.parseQuery(query); 19 | if (query) { 20 | query.forEach((cond) => { 21 | match = match && self.evaluate(item[cond.field], cond.operand, cond.value, strict); 22 | }); 23 | } 24 | return match; 25 | } 26 | parseQuery (query) { 27 | let res = []; 28 | if (!Array.isArray(query)) { 29 | query = [query]; 30 | } 31 | query.forEach((cond) => { 32 | for (let key in cond) { 33 | let item = cond[key]; 34 | if (typeof item === 'object') { 35 | let condition = Object.keys(item); 36 | res.push({ 37 | field: key, 38 | operand: condition[0], 39 | value: item[condition], 40 | }); 41 | } else { 42 | res.push({ 43 | field: key, 44 | operand: '$eq', 45 | value: item, 46 | }); 47 | } 48 | } 49 | }); 50 | return res; 51 | } 52 | evaluate (val1, op, val2, strict) { 53 | switch (op) { 54 | case '$gt': 55 | return val1 > val2; 56 | case '$lt': 57 | return val1 < val2; 58 | case '$gte': 59 | return val1 >= val2; 60 | case '$lte': 61 | return val1 <= val2; 62 | case '$ne': 63 | return strict ? val1 !== val2 : val1 != val2; 64 | case '$eq': 65 | return (typeof val2 === 'function') ? val2(val1) : (strict ? val1 === val2 : val1 == val2); 66 | case '$like': 67 | return new RegExp(val2).test(val1); 68 | } 69 | } 70 | async createDatabase () { 71 | await AsyncStorage.setItem(this.dbName, JSON.stringify({})); 72 | return this.getDatabase(); 73 | } 74 | async getDatabase () { 75 | return new Promise(async(resolve, reject) => { 76 | let database = await AsyncStorage.getItem(this.dbName); 77 | if (database) { 78 | resolve(Object.assign({}, JSON.parse(database))); 79 | } else { 80 | resolve(this.createDatabase()); 81 | } 82 | }); 83 | } 84 | async initCollection () { 85 | if (!this.memory.database) { 86 | this.memory.database = await this.getDatabase(); 87 | } 88 | const database = this.memory.database; 89 | const capped = this.capped; 90 | this.collection = database[this.collectionName] ? database[this.collectionName] : { 91 | 'totalrows': 0, 92 | 'autoinc': 0, 93 | 'maxrows': capped.max || Number.MAX_VALUE, 94 | 'unique': capped.unique && (Array.isArray(capped.unique) ? capped.unique : [capped.unique]), 95 | 'rows': {}, 96 | }; 97 | database[this.collectionName] = database[this.collectionName] || this.collection; 98 | } 99 | async insert (data) { 100 | await this.initCollection(); 101 | return new Promise(async(resolve, reject) => { 102 | try { 103 | let col = this.collection; 104 | let rows = col['rows']; 105 | let canInsert = true; 106 | 107 | if (col.unique) { 108 | let query = {}; 109 | col.unique.forEach((ii) => { query[ii] = { $ne: data[ii] }; }); 110 | for (let _id in rows) { 111 | let row = rows[_id]; 112 | if (!this.checkMatch(row, query, true)) { 113 | canInsert = false; 114 | reject({ message: 'unique reject', query: query }); 115 | break; 116 | } 117 | } 118 | } 119 | if (canInsert) { 120 | let autoinc = col.autoinc++; 121 | data._id = autoinc; 122 | if (col.totalrows >= col.maxrows) { 123 | let key = Object.keys(col.rows)[0]; 124 | delete col.rows[key]; 125 | col.totalrows--; 126 | } 127 | col.rows[autoinc] = data; 128 | col.totalrows++; 129 | 130 | let database = this.memory.database; 131 | database[this.collectionName] = col; 132 | await AsyncStorage.setItem(this.dbName, JSON.stringify(database)); 133 | resolve(data); 134 | } 135 | } catch (error) { 136 | console.error('Mongoose error: ' + error.message); 137 | } 138 | }); 139 | } 140 | async update (data, query, params) { 141 | params = params || {}; 142 | await this.initCollection(); 143 | return new Promise(async(resolve, reject) => { 144 | let results = []; 145 | let rows = this.collection['rows']; 146 | let limit = params.limit || Number.MAX_VALUE; 147 | let offset = params.offset || 0; 148 | let strict = params.strict || this.strict; 149 | let cnt = 0; 150 | try { 151 | for (let row in rows) { 152 | let item = rows[row]; 153 | if (this.checkMatch(item, query, strict)) { 154 | if (++cnt > offset) { 155 | rows[row] = Object.assign({}, item, data); 156 | results.push(item); 157 | if (--limit === 0) { 158 | break; 159 | } 160 | } 161 | } 162 | } 163 | 164 | let database = this.memory.database; 165 | database[this.collectionName] = this.collection; 166 | await AsyncStorage.setItem(this.dbName, JSON.stringify(database)); 167 | resolve(results); 168 | } catch (error) { 169 | console.error('Mongoose error: ' + error.message); 170 | } 171 | }); 172 | } 173 | async upsert (data, query, params) { 174 | params = params || {}; 175 | await this.initCollection(); 176 | return new Promise(async(resolve, reject) => { 177 | try { 178 | const docs = await this.update(data, query, params); 179 | if (docs.length === 0) { 180 | await this.insert(data); 181 | } 182 | resolve(docs); 183 | } catch (error) { 184 | console.error('Mongoose error: ' + error.message); 185 | } 186 | }); 187 | } 188 | async remove (query, params) { 189 | params = params || {}; 190 | await this.initCollection(); 191 | return new Promise(async(resolve, reject) => { 192 | let results = []; 193 | let rows = this.collection['rows']; 194 | let limit = params.limit || Number.MAX_VALUE; 195 | let offset = params.offset || 0; 196 | let strict = params.strict || this.strict; 197 | let cnt = 0; 198 | 199 | try { 200 | for (let row in rows) { 201 | let item = rows[row]; 202 | if (this.checkMatch(item, query, strict)) { 203 | if (++cnt > offset) { 204 | results.push(item); 205 | delete rows[row]; 206 | this.collection['totalrows']--; 207 | if (--limit === 0) { 208 | break; 209 | } 210 | } 211 | } 212 | } 213 | let database = this.memory.database; 214 | database[this.collectionName] = this.collection; 215 | await AsyncStorage.setItem(this.dbName, JSON.stringify(database)); 216 | resolve(results); 217 | } catch (error) { 218 | console.error('Mongoose error: ' + error.message); 219 | } 220 | }); 221 | } 222 | async find (query, params) { 223 | params = params || {}; 224 | await this.initCollection(); 225 | return new Promise((resolve, reject) => { 226 | let results = []; 227 | let rows = this.collection['rows']; 228 | let limit = params.limit || Number.MAX_VALUE; 229 | let offset = params.offset || 0; 230 | let strict = params.strict || this.strict; 231 | let cnt = 0; 232 | for (let row in rows) { 233 | let item = rows[row]; 234 | if (this.checkMatch(item, query, strict)) { 235 | if (++cnt > offset) { 236 | results.push(item); 237 | if (--limit === 0) { 238 | break; 239 | } 240 | } 241 | } 242 | } 243 | resolve(results); 244 | }); 245 | } 246 | async findOne (query, params) { 247 | params = params || {}; 248 | params.limit = 1; 249 | let docs = await this.find(query, params); 250 | return docs ? docs[0] : null; 251 | } 252 | 253 | } 254 | 255 | class Mongoose { 256 | constructor (dbName) { 257 | this.dbName = dbName; 258 | this.memory = { database: false }; 259 | } 260 | collection (collectionName, capped) { 261 | return new Collection(collectionName, this.dbName, capped, this.memory); 262 | } 263 | clear () { 264 | this.memory.database = false; 265 | } 266 | } 267 | 268 | module.exports = Mongoose; 269 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@remobile/react-native-mongoose", 3 | "version": "1.0.1", 4 | "description": "A AsyncStorage based mongoose like storage for react-native", 5 | "main": "index.js", 6 | "author": { 7 | "name": "YunJiang.Fang", 8 | "email": "42550564@qq.com" 9 | }, 10 | "license": "MIT", 11 | "keywords": [ 12 | "react-native", 13 | "react-component", 14 | "ios", 15 | "android", 16 | "mongoose", 17 | "storage", 18 | "store", 19 | "remobile", 20 | "mobile" 21 | ], 22 | "homepage": "https://github.com/remobile/react-native-mongoose", 23 | "bugs": { 24 | "url": "https://github.com/remobile/react-native-mongoose/issues" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git://github.com/remobile/react-native-mongoose.git" 29 | } 30 | } 31 | --------------------------------------------------------------------------------