├── lib ├── store.js └── supermarket.js ├── package.json ├── readme.txt └── test ├── all.js ├── filter.js ├── foreach.js ├── get_set.js ├── json.js ├── length.js ├── multiple_dbs.js ├── multiple_get_set.js ├── multiple_set.js ├── queue.js └── set_remove.js /lib/store.js: -------------------------------------------------------------------------------- 1 | var SQLite = require('pksqlite').Database; 2 | var EventEmitter = require('events').EventEmitter; 3 | var normalize = require('path').normalize; 4 | var Lazy = require('lazy'); 5 | 6 | var openDbs = {}; 7 | 8 | module.exports = Store; 9 | Store.prototype = new EventEmitter; 10 | function Store(opts, cb) { 11 | if (!(this instanceof Store)) return new Store(opts, cb); 12 | if (cb === undefined) cb = function () {}; 13 | var self = this; 14 | 15 | if (typeof opts === 'string') { 16 | var opts = { 17 | filename : opts, 18 | json : false 19 | } 20 | } 21 | 22 | if (opts.filename === undefined) 23 | throw new Error('Filename was not specified'); 24 | 25 | if (opts.filename != ':memory:') { 26 | if (opts.filename[0] != '/') 27 | opts.filename = process.cwd() + '/' + opts.filename; 28 | 29 | opts.filename = normalize(opts.filename); 30 | } 31 | 32 | opts.json = opts.json || false; 33 | 34 | var actionQueue = []; 35 | var ready = false; 36 | 37 | function queue(action, args) { 38 | actionQueue.push({ action : action, args : [].slice.call(args) }); 39 | } 40 | 41 | function initStoreTable() { 42 | var hadRow = false; 43 | db.query( 44 | "SELECT * FROM SQLITE_MASTER WHERE type='table' AND name='store'", 45 | function (error, row) { 46 | if (error) { 47 | self.emit('error', error); 48 | cb(error); 49 | return; 50 | } 51 | if (row === undefined) { 52 | if (hadRow) { 53 | self.emit('ready'); 54 | 55 | cb(undefined, self); 56 | } 57 | else { 58 | db.query( 59 | "CREATE TABLE store (key TEXT UNIQUE, value BLOB)", 60 | function (error) { 61 | if (error) { 62 | self.emit('error', error); 63 | cb(error); 64 | } 65 | else { 66 | self.emit('ready'); 67 | cb(undefined, self); 68 | } 69 | } 70 | ); 71 | } 72 | } 73 | hadRow = true; 74 | } 75 | ); 76 | } 77 | 78 | self.set = function (key, value, cb) { 79 | if (!ready) { 80 | queue(self.set, arguments); 81 | return; 82 | } 83 | if (cb === undefined) cb = function () {} 84 | db.query( 85 | "INSERT OR REPLACE INTO store (key, value) VALUES (?, ?)", 86 | [key, opts.json ? JSON.stringify(value) : value], 87 | function (error) { 88 | if (error) { 89 | self.emit('error', error); 90 | cb(error); 91 | } 92 | else { 93 | cb(undefined, key, value); 94 | } 95 | } 96 | ); 97 | }; 98 | 99 | self.get = function (key, cb) { 100 | if (!ready) { 101 | queue(self.get, arguments); 102 | return; 103 | } 104 | if (cb === undefined) cb = function () {} 105 | var hadRow = false; 106 | db.query( 107 | "SELECT value FROM store WHERE key = ?", 108 | [key], 109 | function (error, row) { 110 | if (error) { 111 | self.emit('error', error); 112 | cb(error); 113 | } 114 | else if (row === undefined) { 115 | if (hadRow) return; 116 | cb(); 117 | } 118 | else { 119 | cb(undefined, opts.json ? JSON.parse(row.value) : row.value, key); 120 | hadRow = true; 121 | } 122 | } 123 | ); 124 | }; 125 | 126 | self.remove = function (key, cb) { 127 | if (!ready) { 128 | queue(self.remove, arguments); 129 | return; 130 | } 131 | if (cb === undefined) cb = function () {}; 132 | db.query( 133 | "DELETE FROM store WHERE key = ?", 134 | [key], 135 | function (error) { 136 | if (error) { 137 | self.emit('error', error) 138 | cb(error); 139 | } 140 | else { 141 | // silly node-sql doesn't set rowsAffected 142 | // cb(undefined, r.rowsAffected == 1); 143 | cb(undefined); 144 | } 145 | } 146 | ); 147 | }; 148 | 149 | self.length = function (cb) { 150 | if (!ready) { 151 | queue(self.length, arguments); 152 | return; 153 | } 154 | if (cb === undefined) cb = function () {}; 155 | db.query( 156 | "SELECT COUNT(*) as count FROM store", 157 | function (error, row) { 158 | if (error) { 159 | self.emit('error', error); 160 | cb(error); 161 | } 162 | else if (row) { 163 | cb(undefined, row.count); 164 | } 165 | } 166 | ); 167 | } 168 | 169 | Object.keys(Lazy()).forEach(function (name) { 170 | self[name] = function () { 171 | var lazy = Lazy(self.stream()); 172 | return lazy[name].apply(lazy, arguments); 173 | } 174 | }); 175 | 176 | self.stream = function (emitter) { 177 | if (!emitter) emitter = new EventEmitter; 178 | 179 | if (!ready) { 180 | queue(self.stream.bind(self,emitter), arguments); 181 | return emitter; 182 | } 183 | 184 | db.query( 185 | "SELECT * FROM store", 186 | function (error, row) { 187 | if (error) { 188 | emitter.emit('error', error); 189 | self.emit('error', error); 190 | } 191 | else if (row === undefined) { 192 | emitter.emit('end'); 193 | } 194 | else { 195 | emitter.emit('data', { 196 | key : row.key, 197 | value : opts.json ? JSON.parse(row.value) : row.value, 198 | }); 199 | } 200 | } 201 | ); 202 | 203 | return emitter; 204 | } 205 | 206 | self.all = function (cb) { 207 | if (!ready) { 208 | queue(self.all, arguments); 209 | return; 210 | } 211 | self 212 | .on('error', function (err) { 213 | self.emit('error', err); 214 | cb(err); 215 | }) 216 | .join(function (rows) { cb( 217 | undefined, 218 | rows.map(function (r) { return r.key }), 219 | rows.map(function (r) { return r.value }) 220 | ) }) 221 | ; 222 | }; 223 | 224 | self.on('ready', function () { 225 | ready = true; 226 | actionQueue.forEach(function (action) { 227 | action.action.apply(self, action.args); 228 | }); 229 | delete actionQueue; 230 | }); 231 | 232 | if (openDbs[opts.filename]) { 233 | var db = openDbs[opts.filename]; 234 | cb(undefined, db); 235 | return; 236 | } 237 | else { 238 | var db = new SQLite(); 239 | db.open(opts.filename, function (error) { 240 | if (error) { 241 | self.emit('error', error); 242 | delete openDbs[opts.filename]; 243 | cb(error); 244 | } 245 | initStoreTable(); 246 | }); 247 | if (opts.filename != ':memory:') 248 | openDbs[opts.filename] = self; 249 | } 250 | }; 251 | 252 | -------------------------------------------------------------------------------- /lib/supermarket.js: -------------------------------------------------------------------------------- 1 | module.exports = module.exports.Store = require('./store') 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "supermarket", 3 | "version": "1.1.3", 4 | "description": "A key/value store based on sqlite for node.js", 5 | "main": "./lib/supermarket.js", 6 | "modules" : { 7 | "index" : "./lib/supermarket.js" 8 | }, 9 | "keywords": [ 10 | "sqlite", 11 | "nosql", 12 | "key value store", 13 | "database" 14 | ], 15 | "author": { 16 | "name": "Peteris Krumins", 17 | "email": "peteris.krumins@gmail.com", 18 | "web": "http://www.catonmat.net", 19 | "twitter": "pkrumins" 20 | }, 21 | "license": "MIT", 22 | "repository": { 23 | "type": "git", 24 | "url": "http://github.com/pkrumins/node-supermarket.git" 25 | }, 26 | "engines": { 27 | "node": ">=0.1.93" 28 | }, 29 | "dependencies" : { 30 | "pksqlite": ">=0.0.3", 31 | "lazy": ">=1.0.0" 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | This is a key/value store based on sqlite for node.js that actually works. 2 | 3 | It was written by Peteris Krumins (peter@catonmat.net). 4 | His blog is at http://www.catonmat.net -- good coders code, great reuse. 5 | 6 | ------------------------------------------------------------------------------ 7 | 8 | It's very simple to use, first import the module: 9 | 10 | var Store = require('supermarket'); 11 | 12 | Then create a new instance of Store, passing in the database filename and 13 | continuation that gets called when the database has been opened, 14 | 15 | Store('users.db', function (error, db) { 16 | // ... you can use db here ... check for error, too ... 17 | }); 18 | 19 | It provides .set and .get methods that are also continuations, 20 | 21 | Store('users.db', function (err, db) { 22 | db.set('pkrumins', 'cool dude', function (error) { 23 | // value 'pkrumins' is now set to 'cool dude' 24 | db.get('pkrumins', function (error, value, key) { 25 | console.log(value); // cool dude 26 | }); 27 | }); 28 | }); 29 | 30 | If you wish to store JSON objects in the database, you may pass a dict with the 31 | key 'json' set to true and the key 'filename' for file as the first argument to 32 | Store, 33 | 34 | Store({ filename : 'objects.db', json : true }, 35 | function (error, db) { 36 | // now any .set and .get operations will call JSON.stringify 37 | // and JSON.parse on values. 38 | } 39 | ); 40 | 41 | ------------. 42 | | Outdated: | The following docs are outdated as we just moved supermarket to node-lazy! 43 | '-----------' 44 | 45 | It also has .filter function that takes a predicate, callback and done function. 46 | The .filter function calls callback on each row for which predicate is true. 47 | After it's done filtering, it calls done function. 48 | 49 | Here is an example: 50 | 51 | Store({ filename : 'users.db', json : true }, function (err, db) { 52 | var users = []; 53 | db.filter( 54 | function (user, userInfo) { //1// 55 | return userInfo.age < 20 56 | }, 57 | function (err, user, userInfo) { //2// 58 | if (err) throw err; 59 | users.push(userInfo); 60 | }, 61 | function () { //3// 62 | console.log("Users younger than 20:"); 63 | users.forEach(function (user) { 64 | console.log(user.name); 65 | }); 66 | } 67 | ); 68 | }); 69 | 70 | The filter function here takes the predicate function //1//, parses each record and 71 | returns true if user's age is less than 20. 72 | Now if the age is less than 20, filter calls callback function //2//, which adds each 73 | user who's younger than 20 to users array. 74 | Once filter has gone through all the records it calls done function //3//, which then 75 | prints out all usernames of youngters. 76 | 77 | Store also has .forEach method that iterates over all of its values, 78 | 79 | Store({ filename: 'users.db', json : true }, function (db) { 80 | db.forEach( 81 | function (err, key, val) { 82 | if (err) throw err; 83 | console.log("User " + key + " is " + val.age + " old."); 84 | }, 85 | function () { 86 | console.log("Done with all users."); 87 | } 88 | ); 89 | }); 90 | 91 | forEach takes a callback and done function. Very similar to .filter. 92 | 93 | It also has .all method that returns all keys and all values, 94 | 95 | Store('users.db', function (db) { 96 | db.all(function (err, users, userInfos) { 97 | // all users in users (keys) 98 | // all user infos in userInfos (values) 99 | }); 100 | }); 101 | 102 | 103 | Another method is .length that returns the number of elements in the store, 104 | 105 | Store('users.db', function (db) { 106 | db.length(function (len) { 107 | console.log("There are " + len + " users in users.db database"); 108 | }); 109 | }); 110 | 111 | This library doesn't end here, our (my and substack's) is to create an object 112 | store, where you can just dump the whole js objects, and then restore them back, 113 | map, filter and fold on them, etc. 114 | 115 | ------------------------------------------------------------------------------ 116 | 117 | Have fun storing those keys and values! 118 | 119 | 120 | Sincerely, 121 | Peteris Krumins 122 | http://www.catonmat.net 123 | 124 | -------------------------------------------------------------------------------- /test/all.js: -------------------------------------------------------------------------------- 1 | var Store = require('supermarket'); 2 | 3 | exports['all'] = function (assert) { 4 | Store(':memory:', function (err, db) { 5 | assert.ok(!err); 6 | 7 | range(0,100).forEach(function (i) { 8 | var key = 'key' + (i).toString(); 9 | db.set(key, i, function (err, k, v) { 10 | assert.ok(!err); 11 | if (v == 99) { 12 | test_all(); 13 | } 14 | }); 15 | }); 16 | 17 | function test_all() { 18 | var expectedKeys = []; 19 | var expectedVals = []; 20 | range(0,100).forEach(function (i) { 21 | expectedKeys.push('key' + (i).toString()); 22 | expectedVals.push(i); 23 | }); 24 | db.all(function (err, keys, vals) { 25 | function sorter(a,b) { return a-b } 26 | assert.eql(expectedKeys.sort(), keys.sort()); 27 | assert.eql(expectedVals, vals.sort(sorter)); 28 | }); 29 | } 30 | }); 31 | } 32 | 33 | function range(i, j) { 34 | var ret = []; 35 | for (; i