@eldoy/webdb 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -167
- package/index.js +148 -220
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,279 +1,261 @@
|
|
|
1
1
|
# WebDB
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A small, fast document database API with a portable query language and native backend execution.
|
|
4
|
+
|
|
5
|
+
WebDB exposes a **single, well-defined interface** (`get` / `set`) designed to cover most web application use cases without sacrificing performance or backend features.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
4
10
|
|
|
5
|
-
### Installation
|
|
6
11
|
```sh
|
|
7
12
|
npm i @eldoy/webdb
|
|
8
13
|
```
|
|
9
14
|
|
|
10
|
-
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
11
18
|
|
|
12
19
|
```js
|
|
13
20
|
var webdb = require('@eldoy/webdb')
|
|
14
21
|
var db = webdb('http://admin:mysecretpassword@localhost:5984')
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
WebDB supports **named collections**:
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
var users = db('user')
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Databases are created automatically on first use.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Insert
|
|
35
|
+
|
|
36
|
+
### Insert one document
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
var doc = await users.set({ name: 'Alice' })
|
|
40
|
+
console.log(doc.id)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The input object may be mutated to attach `id`.
|
|
44
|
+
|
|
45
|
+
---
|
|
15
46
|
|
|
16
|
-
|
|
17
|
-
var doc = await db('user').create({ name: 'Heimdal' })
|
|
47
|
+
### Bulk insert
|
|
18
48
|
|
|
19
|
-
|
|
20
|
-
var
|
|
49
|
+
```js
|
|
50
|
+
var docs = await users.set([
|
|
21
51
|
{ name: 'A' },
|
|
22
52
|
{ name: 'B' }
|
|
23
53
|
])
|
|
54
|
+
```
|
|
24
55
|
|
|
25
|
-
|
|
26
|
-
var n = await db('user').update(
|
|
27
|
-
{ name: 'A' },
|
|
28
|
-
{ active: true }
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
// Set (update exactly one)
|
|
32
|
-
var updated = await db('user').set(
|
|
33
|
-
{ name: 'A' },
|
|
34
|
-
{ name: 'B', active: true }
|
|
35
|
-
)
|
|
56
|
+
Returns the inserted documents (same object references).
|
|
36
57
|
|
|
37
|
-
|
|
38
|
-
var removed = await db('user').remove(
|
|
39
|
-
{ name: 'B' }
|
|
40
|
-
)
|
|
58
|
+
---
|
|
41
59
|
|
|
42
|
-
|
|
43
|
-
var doc = await db('user').put(
|
|
44
|
-
{ email: 'a@example.com' },
|
|
45
|
-
{ email: 'a@example.com', name: 'Alice' }
|
|
46
|
-
)
|
|
60
|
+
## Query (Read)
|
|
47
61
|
|
|
48
|
-
|
|
49
|
-
var doc = await db('user').get({ name: 'Heimdal' })
|
|
62
|
+
### Get first matching document
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
await
|
|
53
|
-
|
|
54
|
-
['name', 'email']
|
|
55
|
-
])
|
|
64
|
+
```js
|
|
65
|
+
var doc = await users.get({ name: 'Alice' })
|
|
66
|
+
```
|
|
56
67
|
|
|
57
|
-
|
|
58
|
-
var docs = await db('user').find({ name: 'Heimdal' })
|
|
68
|
+
Returns `null` if no match exists.
|
|
59
69
|
|
|
60
|
-
|
|
61
|
-
var docs = await db('user').find(
|
|
62
|
-
{ name: 'Heimdal' },
|
|
63
|
-
{ sort: [{ name: 'asc' }] }
|
|
64
|
-
)
|
|
70
|
+
---
|
|
65
71
|
|
|
66
|
-
|
|
67
|
-
var docs = await db('user').find(
|
|
68
|
-
{ name: 'Heimdal' },
|
|
69
|
-
{ limit: 1 }
|
|
70
|
-
)
|
|
72
|
+
### Count
|
|
71
73
|
|
|
72
|
-
|
|
73
|
-
var
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
)
|
|
74
|
+
```js
|
|
75
|
+
var r = await users.get({ active: true }, { count: true })
|
|
76
|
+
console.log(r.count)
|
|
77
|
+
```
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
var n = await db('user').delete({ active: false })
|
|
79
|
+
---
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
var n = await db('user').count({ name: 'Heimdal' })
|
|
81
|
+
### Streaming / batch reads
|
|
83
82
|
|
|
84
|
-
|
|
85
|
-
await
|
|
83
|
+
```js
|
|
84
|
+
await users.get(
|
|
86
85
|
{ active: true },
|
|
87
|
-
{
|
|
86
|
+
{ batch: 100, sort: { created: 1 } },
|
|
88
87
|
async function (docs) {
|
|
89
|
-
//
|
|
88
|
+
// process a batch
|
|
90
89
|
}
|
|
91
90
|
)
|
|
92
|
-
|
|
93
|
-
// Drop a specific database
|
|
94
|
-
await db('user').drop()
|
|
95
|
-
|
|
96
|
-
// Server-level compact
|
|
97
|
-
await db.compact('user')
|
|
98
|
-
|
|
99
|
-
// Drop all databases
|
|
100
|
-
await db.drop()
|
|
101
91
|
```
|
|
102
92
|
|
|
103
|
-
|
|
93
|
+
Streaming controls **delivery**, not execution.
|
|
94
|
+
Internal buffering is allowed.
|
|
95
|
+
|
|
96
|
+
---
|
|
104
97
|
|
|
105
|
-
|
|
98
|
+
## Query Operators
|
|
106
99
|
|
|
107
|
-
|
|
108
|
-
Mango supports standard comparison operators inside selectors:
|
|
100
|
+
Supported predicates:
|
|
109
101
|
|
|
110
102
|
```
|
|
111
|
-
$eq
|
|
112
|
-
$
|
|
113
|
-
$
|
|
114
|
-
$
|
|
115
|
-
$
|
|
116
|
-
$
|
|
117
|
-
$regex regular expression matching
|
|
103
|
+
$eq $ne
|
|
104
|
+
$gt $gte
|
|
105
|
+
$lt $lte
|
|
106
|
+
$in $nin
|
|
107
|
+
$regex
|
|
108
|
+
$exists
|
|
118
109
|
```
|
|
119
110
|
|
|
120
|
-
|
|
111
|
+
Examples:
|
|
121
112
|
|
|
122
113
|
```js
|
|
123
|
-
await
|
|
124
|
-
|
|
125
|
-
})
|
|
114
|
+
await users.get({ age: { $gte: 18 } })
|
|
115
|
+
await users.get({ email: { $regex: '@example.com$' } })
|
|
126
116
|
```
|
|
127
117
|
|
|
128
|
-
|
|
118
|
+
Logical operators:
|
|
129
119
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
```
|
|
133
|
-
$and
|
|
134
|
-
$or
|
|
135
|
-
$not
|
|
136
|
-
$nor
|
|
120
|
+
```js
|
|
121
|
+
$and $or $not
|
|
137
122
|
```
|
|
138
123
|
|
|
139
124
|
Example:
|
|
140
125
|
|
|
141
126
|
```js
|
|
142
|
-
await
|
|
127
|
+
await users.get({
|
|
143
128
|
$or: [{ role: 'admin' }, { active: true }]
|
|
144
129
|
})
|
|
145
130
|
```
|
|
146
131
|
|
|
147
|
-
|
|
132
|
+
---
|
|
148
133
|
|
|
149
|
-
Sorting
|
|
134
|
+
## Sorting, Limiting, Projection
|
|
150
135
|
|
|
151
|
-
|
|
152
|
-
await db('user').index([['created']])
|
|
136
|
+
### Sort
|
|
153
137
|
|
|
154
|
-
|
|
138
|
+
```js
|
|
139
|
+
await users.get(
|
|
155
140
|
{},
|
|
156
|
-
{ sort:
|
|
141
|
+
{ sort: { created: 1 } }
|
|
157
142
|
)
|
|
158
143
|
```
|
|
159
144
|
|
|
160
|
-
|
|
145
|
+
Sorting may require backend support or indexes.
|
|
146
|
+
If unsupported, the adapter may throw.
|
|
161
147
|
|
|
162
|
-
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
### Limit / skip
|
|
163
151
|
|
|
164
152
|
```js
|
|
165
|
-
await
|
|
153
|
+
await users.get({}, { skip: 10, limit: 5 })
|
|
166
154
|
```
|
|
167
155
|
|
|
168
|
-
|
|
156
|
+
---
|
|
169
157
|
|
|
170
|
-
|
|
158
|
+
### Projection (fields)
|
|
171
159
|
|
|
172
160
|
```js
|
|
173
|
-
await
|
|
161
|
+
await users.get(
|
|
174
162
|
{},
|
|
175
|
-
{ fields:
|
|
163
|
+
{ fields: { name: true, email: true } }
|
|
176
164
|
)
|
|
177
165
|
```
|
|
178
166
|
|
|
179
|
-
|
|
167
|
+
Notes:
|
|
180
168
|
|
|
181
|
-
|
|
182
|
-
|
|
169
|
+
* Projection is inclusive if any field is `true`
|
|
170
|
+
* `id` is included by default
|
|
171
|
+
* Excluding `id` is **best-effort**
|
|
172
|
+
* Adapters may still return `id`
|
|
183
173
|
|
|
184
|
-
|
|
185
|
-
* correct sorting
|
|
186
|
-
* correct `$gt` / `$lt` range queries
|
|
174
|
+
---
|
|
187
175
|
|
|
188
|
-
|
|
176
|
+
## Update
|
|
177
|
+
|
|
178
|
+
### Update matching documents
|
|
189
179
|
|
|
190
180
|
```js
|
|
191
|
-
await
|
|
192
|
-
|
|
193
|
-
}
|
|
181
|
+
var r = await users.set(
|
|
182
|
+
{ active: false },
|
|
183
|
+
{ active: true }
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
console.log(r.n)
|
|
194
187
|
```
|
|
195
188
|
|
|
196
|
-
|
|
189
|
+
Rules:
|
|
197
190
|
|
|
198
|
-
|
|
191
|
+
* Shallow merge
|
|
192
|
+
* `undefined` removes a field
|
|
193
|
+
* `null` sets field to `null`
|
|
199
194
|
|
|
200
|
-
|
|
195
|
+
---
|
|
201
196
|
|
|
202
|
-
|
|
203
|
-
* `limit`
|
|
204
|
-
* `sort`
|
|
205
|
-
* `fields`
|
|
206
|
-
* standard selectors
|
|
197
|
+
## Delete
|
|
207
198
|
|
|
208
|
-
|
|
199
|
+
### Delete matching documents
|
|
209
200
|
|
|
210
201
|
```js
|
|
211
|
-
await
|
|
212
|
-
|
|
213
|
-
{ size: 100, sort: [{ created: 'asc' }] },
|
|
214
|
-
async function (docs) {
|
|
215
|
-
// process chunk
|
|
216
|
-
}
|
|
217
|
-
)
|
|
202
|
+
var r = await users.set({ inactive: true }, null)
|
|
203
|
+
console.log(r.n)
|
|
218
204
|
```
|
|
219
205
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
### Notes on Indexing, Sorting, and Mango Queries
|
|
206
|
+
---
|
|
224
207
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
Mango performs best when the selector matches an existing index.
|
|
228
|
-
Any field used in `{ selector: … }` benefits from being part of an index, but it is not required unless sorting is used.
|
|
229
|
-
|
|
230
|
-
**2. Sorting requires indexing**
|
|
231
|
-
|
|
232
|
-
Mango enforces that **every field in the sort must be indexed**.
|
|
233
|
-
Example:
|
|
208
|
+
### Clear collection
|
|
234
209
|
|
|
235
210
|
```js
|
|
236
|
-
await
|
|
237
|
-
await db('user').find({}, { sort: [{ age: 'asc' }] }) // valid
|
|
211
|
+
await users.set({}, null)
|
|
238
212
|
```
|
|
239
213
|
|
|
240
|
-
|
|
214
|
+
Deletes all documents in the collection.
|
|
241
215
|
|
|
242
|
-
|
|
216
|
+
---
|
|
243
217
|
|
|
244
|
-
|
|
218
|
+
## Adapter Extensions (Optional)
|
|
219
|
+
|
|
220
|
+
Adapters may expose additional APIs outside dbspec:
|
|
245
221
|
|
|
246
222
|
```js
|
|
247
|
-
await
|
|
223
|
+
await users.drop() // drop a collection
|
|
224
|
+
await db.drop() // drop all collections
|
|
225
|
+
await db.compact('user')
|
|
226
|
+
await db.info()
|
|
248
227
|
```
|
|
249
228
|
|
|
250
|
-
|
|
229
|
+
These are **adapter-specific** and non-portable.
|
|
251
230
|
|
|
252
|
-
|
|
231
|
+
---
|
|
253
232
|
|
|
254
|
-
|
|
255
|
-
Unindexed projection works fine; indexing does not affect `fields`.
|
|
233
|
+
## Escape Hatch (`data`)
|
|
256
234
|
|
|
257
|
-
|
|
235
|
+
Native backend access is available via:
|
|
258
236
|
|
|
259
|
-
|
|
260
|
-
|
|
237
|
+
```js
|
|
238
|
+
users.data
|
|
239
|
+
```
|
|
261
240
|
|
|
262
|
-
|
|
241
|
+
This exposes the underlying client directly.
|
|
263
242
|
|
|
264
|
-
|
|
243
|
+
Use of `data` is **explicitly non-portable** and bypasses dbspec guarantees.
|
|
265
244
|
|
|
266
|
-
|
|
245
|
+
---
|
|
267
246
|
|
|
268
|
-
|
|
247
|
+
## Design Notes
|
|
269
248
|
|
|
270
|
-
|
|
249
|
+
* The reference implementation prioritizes **native execution**
|
|
250
|
+
* No client-side emulation of query semantics
|
|
251
|
+
* Performance scales with the backend
|
|
252
|
+
* Most web apps do not require the escape hatch
|
|
253
|
+
* When switching adapters, application query logic remains stable
|
|
271
254
|
|
|
272
|
-
|
|
273
|
-
Use `doc.id` after `create()`, and `_id` for all queries and stored documents.
|
|
255
|
+
---
|
|
274
256
|
|
|
275
|
-
|
|
257
|
+
## License
|
|
276
258
|
|
|
277
|
-
|
|
259
|
+
ISC
|
|
278
260
|
|
|
279
|
-
|
|
261
|
+
Created by [Vidar Eldøy](https://eldoy.com)
|
package/index.js
CHANGED
|
@@ -3,14 +3,10 @@ var nano = require('nano')
|
|
|
3
3
|
module.exports = function (url) {
|
|
4
4
|
var server = nano(url)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
return
|
|
6
|
+
function db(name) {
|
|
7
|
+
return collection(name, server)
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
//
|
|
11
|
-
// SERVER-LEVEL OPS (drop all, compact specific)
|
|
12
|
-
//
|
|
13
|
-
|
|
14
10
|
db.drop = async function () {
|
|
15
11
|
var list = await server.db.list()
|
|
16
12
|
for (var i = 0; i < list.length; i++) {
|
|
@@ -22,266 +18,198 @@ module.exports = function (url) {
|
|
|
22
18
|
await server.db.compact(name)
|
|
23
19
|
}
|
|
24
20
|
|
|
21
|
+
db.info = async function () {
|
|
22
|
+
return server.info()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
25
|
return db
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
function collection(name, server) {
|
|
29
|
+
var handle = null
|
|
30
|
+
var initPromise = null
|
|
31
31
|
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
// CRUD
|
|
36
|
-
//
|
|
32
|
+
function init() {
|
|
33
|
+
if (initPromise) return initPromise
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
initPromise = (async function () {
|
|
36
|
+
try {
|
|
37
|
+
await server.db.get(name)
|
|
38
|
+
} catch (e) {
|
|
39
|
+
try {
|
|
40
|
+
await server.db.create(name)
|
|
41
|
+
} catch (e2) {}
|
|
42
|
+
}
|
|
43
|
+
handle = server.db.use(name)
|
|
44
|
+
return handle
|
|
45
|
+
})()
|
|
42
46
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
await db.bulk({ docs: docs })
|
|
46
|
-
return docs.length
|
|
47
|
-
},
|
|
47
|
+
return initPromise
|
|
48
|
+
}
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
// create if not found
|
|
59
|
-
if (!r.docs.length) {
|
|
60
|
-
var created = await dbi.insert(update)
|
|
61
|
-
return {
|
|
62
|
-
_id: created.id,
|
|
63
|
-
_rev: created.rev,
|
|
64
|
-
...update
|
|
50
|
+
var data = new Proxy(
|
|
51
|
+
{},
|
|
52
|
+
{
|
|
53
|
+
get: function (_, prop) {
|
|
54
|
+
return async function () {
|
|
55
|
+
var db = await init()
|
|
56
|
+
return db[prop].apply(db, arguments)
|
|
65
57
|
}
|
|
66
58
|
}
|
|
59
|
+
}
|
|
60
|
+
)
|
|
67
61
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
var next = {}
|
|
71
|
-
|
|
72
|
-
for (var k in cur) next[k] = cur[k]
|
|
73
|
-
for (var k in update) next[k] = update[k]
|
|
74
|
-
|
|
75
|
-
var res = await dbi.insert(next)
|
|
76
|
-
|
|
77
|
-
next._id = res.id
|
|
78
|
-
next._rev = res.rev
|
|
79
|
-
return next
|
|
80
|
-
},
|
|
62
|
+
return {
|
|
63
|
+
data: data,
|
|
81
64
|
|
|
82
|
-
|
|
83
|
-
var db = await
|
|
84
|
-
|
|
85
|
-
if (!r.docs.length) return 0
|
|
65
|
+
get: async function (query, opts, onBatch) {
|
|
66
|
+
var db = await init()
|
|
67
|
+
opts = opts || {}
|
|
86
68
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
var next = {}
|
|
91
|
-
for (var k in cur) next[k] = cur[k]
|
|
92
|
-
for (var k in update) next[k] = update[k]
|
|
93
|
-
out.push(next)
|
|
69
|
+
if (opts.count) {
|
|
70
|
+
var r = await db.find({ selector: query })
|
|
71
|
+
return { count: r.docs.length }
|
|
94
72
|
}
|
|
95
73
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
get: async function (query) {
|
|
101
|
-
var dbi = await ensure(name, server)
|
|
102
|
-
var r = await dbi.find({ selector: query, limit: 1 })
|
|
103
|
-
return r.docs[0] || null
|
|
104
|
-
},
|
|
105
|
-
|
|
106
|
-
set: async function (query, update) {
|
|
107
|
-
var dbi = await ensure(name, server)
|
|
108
|
-
|
|
109
|
-
// find one
|
|
110
|
-
var r = await dbi.find({
|
|
111
|
-
selector: query,
|
|
112
|
-
limit: 1
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
if (!r.docs.length) return null
|
|
116
|
-
|
|
117
|
-
var cur = r.docs[0]
|
|
118
|
-
var next = {}
|
|
74
|
+
if (onBatch) {
|
|
75
|
+
var batch = opts.batch || 100
|
|
76
|
+
var remaining = opts.limit
|
|
77
|
+
var bookmark = null
|
|
119
78
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
79
|
+
for (;;) {
|
|
80
|
+
var limit = batch
|
|
81
|
+
if (remaining !== undefined && remaining < limit) {
|
|
82
|
+
limit = remaining
|
|
83
|
+
}
|
|
123
84
|
|
|
124
|
-
|
|
125
|
-
|
|
85
|
+
var q = { selector: query, limit: limit }
|
|
86
|
+
if (opts.sort) q.sort = normalizeSort(opts.sort)
|
|
126
87
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return next
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
remove: async function (query) {
|
|
134
|
-
var dbi = await ensure(name, server)
|
|
135
|
-
|
|
136
|
-
// find one
|
|
137
|
-
var r = await dbi.find({
|
|
138
|
-
selector: query,
|
|
139
|
-
limit: 1
|
|
140
|
-
})
|
|
88
|
+
var mf = opts.fields && normalizeFields(opts.fields)
|
|
89
|
+
if (mf) q.fields = mf
|
|
90
|
+
if (bookmark) q.bookmark = bookmark
|
|
141
91
|
|
|
142
|
-
|
|
92
|
+
var r = await db.find(q)
|
|
93
|
+
if (!r.docs.length) break
|
|
143
94
|
|
|
144
|
-
|
|
95
|
+
normalizeDocs(r.docs)
|
|
96
|
+
await onBatch(r.docs)
|
|
145
97
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
_deleted: true
|
|
151
|
-
})
|
|
98
|
+
if (remaining !== undefined) {
|
|
99
|
+
remaining -= r.docs.length
|
|
100
|
+
if (remaining <= 0) break
|
|
101
|
+
}
|
|
152
102
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
103
|
+
if (!r.bookmark || r.docs.length < limit) break
|
|
104
|
+
bookmark = r.bookmark
|
|
105
|
+
}
|
|
106
|
+
return
|
|
156
107
|
}
|
|
157
|
-
},
|
|
158
108
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (!r.docs.length) return 0
|
|
109
|
+
var q = { selector: query }
|
|
110
|
+
if (opts.sort) q.sort = normalizeSort(opts.sort)
|
|
111
|
+
if (opts.limit !== undefined) q.limit = opts.limit
|
|
112
|
+
if (opts.skip) q.skip = opts.skip
|
|
164
113
|
|
|
165
|
-
var
|
|
166
|
-
|
|
167
|
-
var d = r.docs[i]
|
|
168
|
-
out.push({
|
|
169
|
-
_id: d._id,
|
|
170
|
-
_rev: d._rev,
|
|
171
|
-
_deleted: true
|
|
172
|
-
})
|
|
173
|
-
}
|
|
114
|
+
var mf = opts.fields && normalizeFields(opts.fields)
|
|
115
|
+
if (mf) q.fields = mf
|
|
174
116
|
|
|
175
|
-
await
|
|
176
|
-
|
|
117
|
+
var r = await db.find(q)
|
|
118
|
+
normalizeDocs(r.docs)
|
|
119
|
+
return r.docs[0] || null
|
|
177
120
|
},
|
|
178
121
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
//
|
|
122
|
+
set: async function (arg, values) {
|
|
123
|
+
var db = await init()
|
|
182
124
|
|
|
183
|
-
|
|
184
|
-
|
|
125
|
+
if (Array.isArray(arg)) {
|
|
126
|
+
var out = []
|
|
127
|
+
for (var i = 0; i < arg.length; i++) {
|
|
128
|
+
var d = arg[i]
|
|
129
|
+
var r = await db.insert(d)
|
|
130
|
+
d.id = r.id
|
|
131
|
+
out.push(d)
|
|
132
|
+
}
|
|
133
|
+
return out
|
|
134
|
+
}
|
|
185
135
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (opts.fields) q.fields = opts.fields
|
|
136
|
+
if (values === undefined) {
|
|
137
|
+
var r = await db.insert(arg)
|
|
138
|
+
arg.id = r.id
|
|
139
|
+
return arg
|
|
191
140
|
}
|
|
192
141
|
|
|
193
|
-
var r = await
|
|
194
|
-
|
|
195
|
-
|
|
142
|
+
var r = await db.find({ selector: arg })
|
|
143
|
+
if (!r.docs.length) return { n: 0 }
|
|
144
|
+
|
|
145
|
+
if (values === null) {
|
|
146
|
+
var del = []
|
|
147
|
+
for (var i = 0; i < r.docs.length; i++) {
|
|
148
|
+
del.push({
|
|
149
|
+
_id: r.docs[i]._id,
|
|
150
|
+
_rev: r.docs[i]._rev,
|
|
151
|
+
_deleted: true
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
await db.bulk({ docs: del })
|
|
155
|
+
return { n: del.length }
|
|
156
|
+
}
|
|
196
157
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
158
|
+
var upd = []
|
|
159
|
+
for (var i = 0; i < r.docs.length; i++) {
|
|
160
|
+
var cur = r.docs[i]
|
|
161
|
+
var next = {}
|
|
162
|
+
for (var k in cur) next[k] = cur[k]
|
|
200
163
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
164
|
+
for (var k in values) {
|
|
165
|
+
if (values[k] === undefined) delete next[k]
|
|
166
|
+
else next[k] = values[k]
|
|
167
|
+
}
|
|
168
|
+
upd.push(next)
|
|
205
169
|
}
|
|
206
|
-
},
|
|
207
|
-
|
|
208
|
-
//
|
|
209
|
-
// COUNT
|
|
210
|
-
//
|
|
211
170
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
var r = await dbi.find({ selector: query })
|
|
215
|
-
return r.docs.length
|
|
171
|
+
await db.bulk({ docs: upd })
|
|
172
|
+
return { n: upd.length }
|
|
216
173
|
},
|
|
217
174
|
|
|
218
|
-
//
|
|
219
|
-
// DROP (collection-level)
|
|
220
|
-
//
|
|
221
|
-
|
|
222
175
|
drop: async function () {
|
|
223
176
|
try {
|
|
224
177
|
await server.db.destroy(name)
|
|
225
178
|
} catch (e) {}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
batch: async function (query, opt, fn) {
|
|
233
|
-
var dbi = await ensure(name, server)
|
|
234
|
-
|
|
235
|
-
var size = opt && opt.size ? opt.size : 100
|
|
236
|
-
var limit = opt && opt.limit
|
|
237
|
-
var sort = opt && opt.sort
|
|
238
|
-
var fields = opt && opt.fields
|
|
239
|
-
|
|
240
|
-
var bookmark = null
|
|
241
|
-
var remaining = limit
|
|
242
|
-
|
|
243
|
-
for (;;) {
|
|
244
|
-
var effectiveSize = size
|
|
245
|
-
if (remaining && remaining < effectiveSize) {
|
|
246
|
-
effectiveSize = remaining
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
var q = {
|
|
250
|
-
selector: query,
|
|
251
|
-
limit: effectiveSize
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (sort) q.sort = sort
|
|
255
|
-
if (fields) q.fields = fields
|
|
256
|
-
if (bookmark) q.bookmark = bookmark
|
|
257
|
-
|
|
258
|
-
var r = await dbi.find(q)
|
|
259
|
-
var docs = r.docs
|
|
260
|
-
if (!docs.length) break
|
|
261
|
-
|
|
262
|
-
await fn(docs)
|
|
179
|
+
handle = null
|
|
180
|
+
initPromise = null
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
263
184
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
185
|
+
function normalizeSort(sort) {
|
|
186
|
+
var out = []
|
|
187
|
+
for (var k in sort) {
|
|
188
|
+
if (sort[k] === 1) out.push({ [k]: 'asc' })
|
|
189
|
+
else if (sort[k] === -1) out.push({ [k]: 'desc' })
|
|
190
|
+
}
|
|
191
|
+
return out
|
|
192
|
+
}
|
|
268
193
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
194
|
+
function normalizeFields(fields) {
|
|
195
|
+
var include = []
|
|
196
|
+
for (var k in fields) {
|
|
197
|
+
if (fields[k]) {
|
|
198
|
+
include.push(k === 'id' ? '_id' : k)
|
|
272
199
|
}
|
|
273
200
|
}
|
|
201
|
+
if (include.length) {
|
|
202
|
+
if (fields.id !== false && include.indexOf('_id') === -1)
|
|
203
|
+
include.push('_id')
|
|
204
|
+
return include
|
|
205
|
+
}
|
|
206
|
+
return null
|
|
274
207
|
}
|
|
275
208
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
try {
|
|
282
|
-
await server.db.get(name)
|
|
283
|
-
} catch (e) {
|
|
284
|
-
await server.db.create(name)
|
|
209
|
+
function normalizeDocs(docs) {
|
|
210
|
+
for (var i = 0; i < docs.length; i++) {
|
|
211
|
+
docs[i].id = docs[i]._id
|
|
212
|
+
delete docs[i]._id
|
|
213
|
+
delete docs[i]._rev
|
|
285
214
|
}
|
|
286
|
-
return server.db.use(name)
|
|
287
215
|
}
|