@eldoy/webdb 0.1.0 → 0.1.2

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 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
 
@@ -83,6 +83,125 @@ await db.compact('user')
83
83
  await db.drop()
84
84
  ```
85
85
 
86
+ ### Mango Query Options
87
+
88
+ WebDB queries map directly to CouchDB Mango selectors. All Mango operators and options work as expected through `find()` and `batch()`.
89
+
90
+ #### Comparison Operators
91
+ Mango supports standard comparison operators inside selectors:
92
+
93
+ ```
94
+ $eq equal
95
+ $ne not equal
96
+ $gt greater than
97
+ $gte greater than or equal
98
+ $lt less than
99
+ $lte less than or equal
100
+ ```
101
+
102
+ Example:
103
+
104
+ ```js
105
+ await db('user').find({
106
+ age: { $gte: 18 }
107
+ })
108
+ ```
109
+
110
+ #### Logical Operators
111
+
112
+ Combine conditions using logical operators:
113
+
114
+ ```
115
+ $and
116
+ $or
117
+ $not
118
+ $nor
119
+ ```
120
+
121
+ Example:
122
+
123
+ ```js
124
+ await db('user').find({
125
+ $or: [{ role: 'admin' }, { active: true }]
126
+ })
127
+ ```
128
+
129
+ #### Sorting
130
+
131
+ Sorting requires an index on every field in the sort specification:
132
+
133
+ ```js
134
+ await db('user').index([['created']])
135
+
136
+ await db('user').find(
137
+ {},
138
+ { sort: [{ created: 'asc' }] }
139
+ )
140
+ ```
141
+
142
+ #### Limiting
143
+
144
+ Limit returned documents:
145
+
146
+ ```js
147
+ await db('user').find({}, { limit: 10 })
148
+ ```
149
+
150
+ #### Projection (fields)
151
+
152
+ Return only selected fields:
153
+
154
+ ```js
155
+ await db('user').find(
156
+ {},
157
+ { fields: ['name', 'email'] }
158
+ )
159
+ ```
160
+
161
+ #### Date Handling
162
+
163
+ CouchDB stores dates as strings.
164
+ Using ISO-8601 timestamps (`new Date().toISOString()`) enables:
165
+
166
+ * correct lexical comparison
167
+ * correct sorting
168
+ * correct `$gt` / `$lt` range queries
169
+
170
+ Example:
171
+
172
+ ```js
173
+ await db('log').find({
174
+ created: { $gt: "2024-01-01T00:00:00.000Z" }
175
+ })
176
+ ```
177
+
178
+ ISO strings compare and sort in true chronological order.
179
+
180
+ #### Batch Queries
181
+
182
+ `batch()` supports all Mango options:
183
+
184
+ * `size` (page size)
185
+ * `limit`
186
+ * `sort`
187
+ * `fields`
188
+ * standard selectors
189
+
190
+ Example:
191
+
192
+ ```js
193
+ await db('user').batch(
194
+ { created: { $gt: cutoff } },
195
+ { size: 100, sort: [{ created: 'asc' }] },
196
+ async function (docs) {
197
+ // process chunk
198
+ }
199
+ )
200
+ ```
201
+
202
+ All Mango selectors and options work identically in both `find()` and `batch()`.
203
+
204
+
86
205
  ### Notes on Indexing, Sorting, and Mango Queries
87
206
 
88
207
  **1. Selector fields and indexes**
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@eldoy/webdb",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Document database client powered by CouchDB.",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
5
8
  "keywords": [
6
9
  "database",
7
10
  "couchdb",
@@ -1,7 +0,0 @@
1
- module.exports = async function () {
2
- async function setup() {}
3
-
4
- async function teardown() {}
5
-
6
- return { db, setup, teardown }
7
- }
@@ -1,242 +0,0 @@
1
- var webdb = require('../../index.js')
2
- var db = webdb('http://admin:mysecretpassword@localhost:5984')
3
-
4
- beforeEach(async function ({ t }) {
5
- await db.drop()
6
- })
7
-
8
- //
9
- // CRUD: create, bulk, update, get
10
- //
11
-
12
- test('create', async function ({ t }) {
13
- var doc = await db('user').create({ name: 'Heimdal' })
14
- t.ok(doc && doc.id)
15
- })
16
-
17
- test('bulk', async function ({ t }) {
18
- var n = await db('user').bulk([{ name: 'A' }, { name: 'B' }])
19
- t.equal(n, 2)
20
- })
21
-
22
- test('update one', async function ({ t }) {
23
- var doc = await db('user').create({ name: 'Old' })
24
- var n = await db('user').update({ _id: doc.id }, { name: 'New' })
25
- t.equal(n, 1)
26
- })
27
-
28
- test('update many', async function ({ t }) {
29
- await db('user').bulk([{ role: 'x' }, { role: 'x' }, { role: 'y' }])
30
- var n = await db('user').update({ role: 'x' }, { role: 'z' })
31
- t.equal(n, 2)
32
- })
33
-
34
- test('get', async function ({ t }) {
35
- await db('user').create({ name: 'Heimdal' })
36
- var doc = await db('user').get({ name: 'Heimdal' })
37
- t.ok(doc && doc._id)
38
- })
39
-
40
- //
41
- // FIND: base, sort, limit, fields
42
- //
43
-
44
- test('find', async function ({ t }) {
45
- await db('user').bulk([
46
- { name: 'A', age: 1 },
47
- { name: 'A', age: 2 },
48
- { name: 'B', age: 3 }
49
- ])
50
-
51
- var docs = await db('user').find({ name: 'A' })
52
- t.ok(Array.isArray(docs))
53
- t.equal(docs.length, 2)
54
- })
55
-
56
- test('find sort', async function ({ t }) {
57
- await db('user').index([['n']])
58
-
59
- await db('user').bulk([
60
- { name: 'A', n: 3 },
61
- { name: 'A', n: 1 },
62
- { name: 'A', n: 2 }
63
- ])
64
-
65
- var docs = await db('user').find({ name: 'A' }, { sort: [{ n: 'asc' }] })
66
- t.equal(docs[0].n, 1)
67
- t.equal(docs[2].n, 3)
68
- })
69
-
70
- test('find limit', async function ({ t }) {
71
- await db('user').bulk([{ name: 'A' }, { name: 'A' }, { name: 'A' }])
72
- var docs = await db('user').find({ name: 'A' }, { limit: 1 })
73
- t.equal(docs.length, 1)
74
- })
75
-
76
- test('find fields', async function ({ t }) {
77
- await db('user').bulk([{ name: 'A', age: 10 }])
78
- var docs = await db('user').find({ name: 'A' }, { fields: ['name'] })
79
- t.ok(docs[0].name)
80
- t.equal(docs[0].age, undefined)
81
- })
82
-
83
- //
84
- // INDEX
85
- //
86
-
87
- test('index', async function ({ t }) {
88
- await db('user').index([['name', 'email']])
89
- var docs = await db('user').find(
90
- { name: 'X' },
91
- { sort: [{ name: 'asc' }, { email: 'asc' }] }
92
- )
93
- t.ok(Array.isArray(docs))
94
- })
95
-
96
- //
97
- // DELETE: one and many
98
- //
99
-
100
- test('delete one', async function ({ t }) {
101
- var a = await db('user').create({ name: 'X' })
102
- await db('user').create({ name: 'Y' })
103
-
104
- var n = await db('user').delete({ _id: a.id })
105
- t.equal(n, 1)
106
-
107
- var doc = await db('user').get({ _id: a.id })
108
- t.equal(doc, null)
109
- })
110
-
111
- test('delete many', async function ({ t }) {
112
- await db('user').bulk([{ role: 'x' }, { role: 'x' }, { role: 'y' }])
113
- var n = await db('user').delete({ role: 'x' })
114
- t.equal(n, 2)
115
-
116
- var docs = await db('user').find({ role: 'x' })
117
- t.equal(docs.length, 0)
118
- })
119
-
120
- //
121
- // COUNT
122
- //
123
-
124
- test('count', async function ({ t }) {
125
- await db('user').bulk([{ type: 'a' }, { type: 'a' }, { type: 'b' }])
126
- var n = await db('user').count({ type: 'a' })
127
- t.equal(n, 2)
128
- })
129
-
130
- //
131
- // BATCH: base + query + size + sort + limit + fields
132
- //
133
-
134
- test('batch', async function ({ t }) {
135
- await db('user').bulk([{ v: 1 }, { v: 2 }, { v: 3 }, { v: 4 }])
136
-
137
- var collected = []
138
-
139
- await db('user').batch({}, { size: 2 }, async function (docs) {
140
- for (var i = 0; i < docs.length; i++) collected.push(docs[i].v)
141
- })
142
-
143
- t.equal(collected.length, 4)
144
- t.ok(collected.includes(1))
145
- t.ok(collected.includes(4))
146
- })
147
-
148
- test('batch with query', async function ({ t }) {
149
- await db('user').bulk([
150
- { type: 'a', v: 1 },
151
- { type: 'a', v: 2 },
152
- { type: 'b', v: 3 }
153
- ])
154
-
155
- var out = []
156
-
157
- await db('user').batch({ type: 'a' }, { size: 10 }, async function (docs) {
158
- for (var i = 0; i < docs.length; i++) out.push(docs[i].v)
159
- })
160
-
161
- t.equal(out.length, 2)
162
- t.ok(out.includes(1))
163
- t.ok(out.includes(2))
164
- })
165
-
166
- test('batch with size', async function ({ t }) {
167
- await db('user').bulk([{ n: 1 }, { n: 2 }, { n: 3 }, { n: 4 }])
168
-
169
- var chunks = []
170
-
171
- await db('user').batch({}, { size: 2 }, async function (docs) {
172
- chunks.push(docs.length)
173
- })
174
-
175
- t.equal(chunks.length, 2)
176
- t.equal(chunks[0], 2)
177
- t.equal(chunks[1], 2)
178
- })
179
-
180
- test('batch respects sort', async function ({ t }) {
181
- await db('user').index([['n']])
182
-
183
- await db('user').bulk([{ n: 3 }, { n: 1 }, { n: 2 }])
184
-
185
- var list = []
186
-
187
- await db('user').batch(
188
- {},
189
- { size: 1, sort: [{ n: 'asc' }] },
190
- async function (docs) {
191
- list.push(docs[0].n)
192
- }
193
- )
194
-
195
- t.deepEqual(list, [1, 2, 3])
196
- })
197
-
198
- test('batch respects limit', async function ({ t }) {
199
- await db('user').bulk([{ n: 1 }, { n: 2 }, { n: 3 }])
200
-
201
- var list = []
202
-
203
- await db('user').batch({}, { limit: 2 }, async function (docs) {
204
- for (var i = 0; i < docs.length; i++) list.push(docs[i].n)
205
- })
206
-
207
- t.equal(list.length, 2)
208
- })
209
-
210
- test('batch respects fields', async function ({ t }) {
211
- await db('user').bulk([{ name: 'A', age: 10 }])
212
-
213
- var fields = []
214
-
215
- await db('user').batch(
216
- { name: 'A' },
217
- { fields: ['name'] },
218
- async function (docs) {
219
- fields.push(Object.keys(docs[0]))
220
- }
221
- )
222
-
223
- t.deepEqual(fields[0], ['name'])
224
- })
225
-
226
- //
227
- // DATABASE-LEVEL OPS
228
- //
229
-
230
- test('drop database', async function ({ t }) {
231
- await db('user').create({ name: 'A' })
232
- await db('user').drop()
233
- var doc = await db('user').get({ name: 'A' })
234
- t.equal(doc, null)
235
- })
236
-
237
- test('compact', async function ({ t }) {
238
- await db('user').create({ name: 'A' })
239
- await db('user').update({ name: 'A' }, { name: 'B' })
240
- await db.compact('user')
241
- t.ok(true)
242
- })