1 | var Emitter = require('component-emitter')
|
2 | var inherits = require('inherits')
|
3 | var filter = require('filter-object-stream')
|
4 | var filterObject = require('filter-object')
|
5 | var validator = require('is-my-json-valid')
|
6 | var indexer = require('level-simple-indexes')
|
7 | var defaults = require('json-schema-defaults')
|
8 | var sublevel = require('subleveldown')
|
9 | var through = require('through2')
|
10 | var extend = require('extend')
|
11 | var cuid = require('cuid')
|
12 |
|
13 | module.exports = LevelModel
|
14 | inherits(LevelModel, Emitter)
|
15 |
|
16 | function LevelModel (db, opts) {
|
17 | if (!(this instanceof LevelModel)) return new LevelModel(db, opts)
|
18 | Emitter.call(this)
|
19 | var self = this
|
20 |
|
21 | this.schema = filterObject(opts, ['*', '!modelName', '!timestamp', '!indexKeys', '!validateOpts', '!prefix'])
|
22 | this.modelName = opts.modelName
|
23 | this.db = sublevel(db, this.modelName, { valueEncoding: 'json' })
|
24 | this.timestamps = opts.timestamps === undefined ? true : opts.timestamps
|
25 | this.timestamp = opts.timestamp || function () { return new Date(Date.now()).toISOString() }
|
26 | this.indexKeys = opts.indexKeys || []
|
27 |
|
28 | this.schema = extend({
|
29 | title: self.modelName,
|
30 | type: 'object'
|
31 | }, this.schema)
|
32 |
|
33 | this.schema.properties.key = {
|
34 | type: 'string'
|
35 | }
|
36 |
|
37 | if (this.timestamps) {
|
38 | this.schema.properties.created = {
|
39 | type: ['string', 'null'],
|
40 | default: null
|
41 | }
|
42 | this.schema.properties.updated = {
|
43 | type: ['string', 'null'],
|
44 | default: null
|
45 | }
|
46 | }
|
47 |
|
48 | this.schema.required = this.schema.required || []
|
49 | if (this.schema.required.indexOf('key') < 0) {
|
50 | this.schema.required = this.schema.required.concat('key')
|
51 | }
|
52 |
|
53 | this.validateOpts = opts.validateOpts
|
54 | this.validate = validator(this.schema, opts.validateOpts)
|
55 |
|
56 | function map (key, callback) {
|
57 | self.get(key, function (err, val) {
|
58 | callback(err, val)
|
59 | })
|
60 | }
|
61 |
|
62 | var indexOpts = opts.indexOpts || {
|
63 | properties: this.indexKeys,
|
64 | keys: true,
|
65 | values: true,
|
66 | map: map
|
67 | }
|
68 |
|
69 | this.indexDB = sublevel(db, this.modelName + '-index')
|
70 | this.indexer = indexer(this.indexDB, indexOpts)
|
71 | }
|
72 |
|
73 | LevelModel.prototype.create = function (data, callback) {
|
74 | var self = this
|
75 | var key = data.key ? data.key : cuid()
|
76 | if (!data.key) data.key = key
|
77 | data = this.beforeCreate(data)
|
78 | data = extend(defaults(this.schema), data)
|
79 | var validated = this.validate(data)
|
80 | if (!validated) return callback(new Error(JSON.stringify(this.validate.errors)))
|
81 |
|
82 | if (this.timestamps) {
|
83 | data.created = this.timestamp()
|
84 | data.updated = null
|
85 | }
|
86 | this.db.put(key, data, function (err) {
|
87 | if (err) return callback(err)
|
88 |
|
89 | self.indexer.addIndexes(data, function () {
|
90 | self.emit('create', data)
|
91 | callback(null, data)
|
92 | })
|
93 | })
|
94 | }
|
95 |
|
96 | LevelModel.prototype.get = function (key, options, callback) {
|
97 | this.db.get(key, options, callback)
|
98 | }
|
99 |
|
100 | LevelModel.prototype.put =
|
101 | LevelModel.prototype.save = function (key, data, callback) {
|
102 | if (typeof key === 'object') {
|
103 | callback = data
|
104 | data = key
|
105 | key = data.key
|
106 | }
|
107 |
|
108 | if (!key) return this.create(data, callback)
|
109 | return this.update(key, data, callback)
|
110 | }
|
111 |
|
112 | LevelModel.prototype.update = function (key, data, callback) {
|
113 | var self = this
|
114 |
|
115 | if (typeof key === 'object') {
|
116 | callback = data
|
117 | data = key
|
118 | key = data.key
|
119 | }
|
120 |
|
121 | this.get(key, function (err, model) {
|
122 | if (err || !model) return callback(new Error(self.modelName + ' not found with key ' + key))
|
123 | model = extend(model, data)
|
124 | if (self.timestamps) model.updated = self.timestamp()
|
125 | model = self.beforeUpdate(model)
|
126 |
|
127 | var validated = self.validate(data)
|
128 | if (!validated) return callback(new Error(JSON.stringify(self.validate.errors)))
|
129 |
|
130 | self.indexer.updateIndexes(model, function () {
|
131 | self.db.put(key, model, function (err) {
|
132 | self.emit('update', model)
|
133 | callback(err, model)
|
134 | })
|
135 | })
|
136 | })
|
137 | }
|
138 |
|
139 | LevelModel.prototype.del =
|
140 | LevelModel.prototype.delete = function (key, callback) {
|
141 | var self = this
|
142 | this.get(key, function (err, data) {
|
143 | if (err || !data) return callback(err)
|
144 | self.indexer.removeIndexes(data, function () {
|
145 | self.emit('delete', data)
|
146 | self.db.del(key, callback)
|
147 | })
|
148 | })
|
149 | }
|
150 |
|
151 | LevelModel.prototype.createReadStream = function (options) {
|
152 | return this.db.createReadStream(options)
|
153 | }
|
154 |
|
155 | LevelModel.prototype.find =
|
156 | LevelModel.prototype.createFindStream = function (index, options) {
|
157 | return this.indexer.find(index, options)
|
158 | }
|
159 |
|
160 | LevelModel.prototype.findOne = function (index, options, callback) {
|
161 | this.indexer.findOne(index, options, function (err, model) {
|
162 | if (err) return callback(err)
|
163 | if (!model) return callback(new Error('[NotFoundError: model not found with ' + index + ' ' + options + ']'))
|
164 | return callback(null, model)
|
165 | })
|
166 | }
|
167 |
|
168 | LevelModel.prototype.filter =
|
169 | LevelModel.prototype.createFilterStream = function (options) {
|
170 | if (!options.query) var options = { query: options }
|
171 | return this.createReadStream(options).pipe(through.obj(filter(options.query)))
|
172 | }
|
173 |
|
174 | LevelModel.prototype.beforeCreate = function (data) {
|
175 | return data
|
176 | }
|
177 |
|
178 | LevelModel.prototype.beforeUpdate = function (data) {
|
179 | return data
|
180 | }
|