@eldoy/webdb 0.1.1 → 0.3.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 +138 -1
- package/index.js +106 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Document database API backed by CouchDB, exposed through a Mongo-style client.
|
|
|
5
5
|
### Installation
|
|
6
6
|
```sh
|
|
7
7
|
npm i @eldoy/webdb
|
|
8
|
-
|
|
8
|
+
```
|
|
9
9
|
|
|
10
10
|
### Usage
|
|
11
11
|
|
|
@@ -28,6 +28,23 @@ var n = await db('user').update(
|
|
|
28
28
|
{ active: true }
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
+
// Set (update exactly one)
|
|
32
|
+
var updated = await db('user').set(
|
|
33
|
+
{ name: 'A' },
|
|
34
|
+
{ name: 'B', active: true }
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
// Remove (delete exactly one)
|
|
38
|
+
var removed = await db('user').remove(
|
|
39
|
+
{ name: 'B' }
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
// Upsert (create or update one)
|
|
43
|
+
var doc = await db('user').upsert(
|
|
44
|
+
{ email: 'a@example.com' },
|
|
45
|
+
{ email: 'a@example.com', name: 'Alice' }
|
|
46
|
+
)
|
|
47
|
+
|
|
31
48
|
// Get one (first match)
|
|
32
49
|
var doc = await db('user').get({ name: 'Heimdal' })
|
|
33
50
|
|
|
@@ -83,6 +100,126 @@ await db.compact('user')
|
|
|
83
100
|
await db.drop()
|
|
84
101
|
```
|
|
85
102
|
|
|
103
|
+
### Mango Query Options
|
|
104
|
+
|
|
105
|
+
WebDB queries map directly to CouchDB Mango selectors. All Mango operators and options work as expected through `find()` and `batch()`.
|
|
106
|
+
|
|
107
|
+
#### Comparison Operators
|
|
108
|
+
Mango supports standard comparison operators inside selectors:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
$eq equal
|
|
112
|
+
$ne not equal
|
|
113
|
+
$gt greater than
|
|
114
|
+
$gte greater than or equal
|
|
115
|
+
$lt less than
|
|
116
|
+
$lte less than or equal
|
|
117
|
+
$regex regular expression matching
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Example:
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
await db('user').find({
|
|
124
|
+
age: { $gte: 18 }
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### Logical Operators
|
|
129
|
+
|
|
130
|
+
Combine conditions using logical operators:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
$and
|
|
134
|
+
$or
|
|
135
|
+
$not
|
|
136
|
+
$nor
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Example:
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
await db('user').find({
|
|
143
|
+
$or: [{ role: 'admin' }, { active: true }]
|
|
144
|
+
})
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### Sorting
|
|
148
|
+
|
|
149
|
+
Sorting requires an index on every field in the sort specification:
|
|
150
|
+
|
|
151
|
+
```js
|
|
152
|
+
await db('user').index([['created']])
|
|
153
|
+
|
|
154
|
+
await db('user').find(
|
|
155
|
+
{},
|
|
156
|
+
{ sort: [{ created: 'asc' }] }
|
|
157
|
+
)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### Limiting
|
|
161
|
+
|
|
162
|
+
Limit returned documents:
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
await db('user').find({}, { limit: 10 })
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Projection (fields)
|
|
169
|
+
|
|
170
|
+
Return only selected fields:
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
await db('user').find(
|
|
174
|
+
{},
|
|
175
|
+
{ fields: ['name', 'email'] }
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### Date Handling
|
|
180
|
+
|
|
181
|
+
CouchDB stores dates as strings.
|
|
182
|
+
Using ISO-8601 timestamps (`new Date().toISOString()`) enables:
|
|
183
|
+
|
|
184
|
+
* correct lexical comparison
|
|
185
|
+
* correct sorting
|
|
186
|
+
* correct `$gt` / `$lt` range queries
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
await db('log').find({
|
|
192
|
+
created: { $gt: "2024-01-01T00:00:00.000Z" }
|
|
193
|
+
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
ISO strings compare and sort in true chronological order.
|
|
197
|
+
|
|
198
|
+
#### Batch Queries
|
|
199
|
+
|
|
200
|
+
`batch()` supports all Mango options:
|
|
201
|
+
|
|
202
|
+
* `size` (page size)
|
|
203
|
+
* `limit`
|
|
204
|
+
* `sort`
|
|
205
|
+
* `fields`
|
|
206
|
+
* standard selectors
|
|
207
|
+
|
|
208
|
+
Example:
|
|
209
|
+
|
|
210
|
+
```js
|
|
211
|
+
await db('user').batch(
|
|
212
|
+
{ created: { $gt: cutoff } },
|
|
213
|
+
{ size: 100, sort: [{ created: 'asc' }] },
|
|
214
|
+
async function (docs) {
|
|
215
|
+
// process chunk
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
All Mango selectors and options work identically in both `find()` and `batch()`.
|
|
221
|
+
|
|
222
|
+
|
|
86
223
|
### Notes on Indexing, Sorting, and Mango Queries
|
|
87
224
|
|
|
88
225
|
**1. Selector fields and indexes**
|
package/index.js
CHANGED
|
@@ -46,6 +46,39 @@ function api(name, server) {
|
|
|
46
46
|
return docs.length
|
|
47
47
|
},
|
|
48
48
|
|
|
49
|
+
upsert: async function (query, update) {
|
|
50
|
+
var dbi = await ensure(name, server)
|
|
51
|
+
|
|
52
|
+
// find one match
|
|
53
|
+
var r = await dbi.find({
|
|
54
|
+
selector: query,
|
|
55
|
+
limit: 1
|
|
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
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// update existing
|
|
69
|
+
var cur = r.docs[0]
|
|
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
|
+
},
|
|
81
|
+
|
|
49
82
|
update: async function (query, update) {
|
|
50
83
|
var db = await ensure(name, server)
|
|
51
84
|
var r = await db.find({ selector: query })
|
|
@@ -70,39 +103,59 @@ function api(name, server) {
|
|
|
70
103
|
return r.docs[0] || null
|
|
71
104
|
},
|
|
72
105
|
|
|
73
|
-
|
|
74
|
-
// FIND
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
find: async function (selector, opts) {
|
|
106
|
+
set: async function (query, update) {
|
|
78
107
|
var dbi = await ensure(name, server)
|
|
79
108
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
109
|
+
// find one
|
|
110
|
+
var r = await dbi.find({
|
|
111
|
+
selector: query,
|
|
112
|
+
limit: 1
|
|
113
|
+
})
|
|
86
114
|
|
|
87
|
-
|
|
88
|
-
return r.docs
|
|
89
|
-
},
|
|
115
|
+
if (!r.docs.length) return null
|
|
90
116
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
//
|
|
117
|
+
var cur = r.docs[0]
|
|
118
|
+
var next = {}
|
|
94
119
|
|
|
95
|
-
|
|
120
|
+
// merge current doc + update
|
|
121
|
+
for (var k in cur) next[k] = cur[k]
|
|
122
|
+
for (var k in update) next[k] = update[k]
|
|
123
|
+
|
|
124
|
+
// write updated doc
|
|
125
|
+
var res = await dbi.insert(next)
|
|
126
|
+
|
|
127
|
+
// return updated document shape
|
|
128
|
+
next._id = res.id
|
|
129
|
+
next._rev = res.rev
|
|
130
|
+
return next
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
remove: async function (query) {
|
|
96
134
|
var dbi = await ensure(name, server)
|
|
97
|
-
|
|
98
|
-
|
|
135
|
+
|
|
136
|
+
// find one
|
|
137
|
+
var r = await dbi.find({
|
|
138
|
+
selector: query,
|
|
139
|
+
limit: 1
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
if (!r.docs.length) return null
|
|
143
|
+
|
|
144
|
+
var doc = r.docs[0]
|
|
145
|
+
|
|
146
|
+
// mark as deleted
|
|
147
|
+
var res = await dbi.insert({
|
|
148
|
+
_id: doc._id,
|
|
149
|
+
_rev: doc._rev,
|
|
150
|
+
_deleted: true
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
_id: res.id,
|
|
155
|
+
_rev: res.rev
|
|
99
156
|
}
|
|
100
157
|
},
|
|
101
158
|
|
|
102
|
-
//
|
|
103
|
-
// DELETE
|
|
104
|
-
//
|
|
105
|
-
|
|
106
159
|
delete: async function (query) {
|
|
107
160
|
var dbi = await ensure(name, server)
|
|
108
161
|
|
|
@@ -123,6 +176,35 @@ function api(name, server) {
|
|
|
123
176
|
return out.length
|
|
124
177
|
},
|
|
125
178
|
|
|
179
|
+
//
|
|
180
|
+
// FIND
|
|
181
|
+
//
|
|
182
|
+
|
|
183
|
+
find: async function (query, opts) {
|
|
184
|
+
var dbi = await ensure(name, server)
|
|
185
|
+
|
|
186
|
+
var q = { selector: query }
|
|
187
|
+
if (opts) {
|
|
188
|
+
if (opts.sort) q.sort = opts.sort
|
|
189
|
+
if (opts.limit) q.limit = opts.limit
|
|
190
|
+
if (opts.fields) q.fields = opts.fields
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
var r = await dbi.find(q)
|
|
194
|
+
return r.docs
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
//
|
|
198
|
+
// INDEX
|
|
199
|
+
//
|
|
200
|
+
|
|
201
|
+
index: async function (list) {
|
|
202
|
+
var dbi = await ensure(name, server)
|
|
203
|
+
for (var i = 0; i < list.length; i++) {
|
|
204
|
+
await dbi.createIndex({ index: { fields: list[i] } })
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
|
|
126
208
|
//
|
|
127
209
|
// COUNT
|
|
128
210
|
//
|