hide-a-bed 1.2.0 → 2.1.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 +145 -50
- package/impl/crud.mjs +2 -4
- package/impl/patch.mjs +36 -0
- package/index.mjs +6 -2
- package/package.json +1 -1
- package/schema/bulk.mjs +5 -0
- package/schema/crud.mjs +1 -1
- package/schema/patch.mjs +18 -0
package/README.md
CHANGED
|
@@ -1,73 +1,168 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
A simple way to abstract couchdb, and make your interface to the database testable.
|
|
1
|
+
API
|
|
2
|
+
-------------
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
-----
|
|
4
|
+
### Document Operations
|
|
8
5
|
|
|
9
|
-
|
|
6
|
+
#### get(config, id)
|
|
7
|
+
Get a single document by ID.
|
|
8
|
+
- `config`: Object with `couch` URL string
|
|
9
|
+
- `id`: Document ID string
|
|
10
|
+
- Returns: Promise resolving to document object or null if not found
|
|
10
11
|
|
|
12
|
+
```javascript
|
|
13
|
+
const config = { couch: 'http://localhost:5984/mydb' }
|
|
14
|
+
const doc = await get(config, 'doc-123')
|
|
15
|
+
if (doc) {
|
|
16
|
+
console.log(doc._id, doc._rev)
|
|
17
|
+
}
|
|
11
18
|
```
|
|
12
|
-
npm i hide-a-bed --save
|
|
13
|
-
npm i hide-a-bed-stub --save-dev
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
#### put(config, doc)
|
|
21
|
+
Save a document.
|
|
22
|
+
- `config`: Object with `couch` URL string
|
|
23
|
+
- `doc`: Document object with `_id` property
|
|
24
|
+
- Returns: Promise resolving to response with `ok`, `id`, `rev` properties
|
|
16
25
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
startkey: apiResult.startTime,
|
|
24
|
-
endkey: apiResult.endTime
|
|
25
|
-
}
|
|
26
|
-
const queryResults = await db.query(config, '_design/userThings/_view/byTime', query)
|
|
27
|
-
return queryResults.rows
|
|
26
|
+
```javascript
|
|
27
|
+
const config = { couch: 'http://localhost:5984/mydb' }
|
|
28
|
+
const doc = {
|
|
29
|
+
_id: 'doc-123',
|
|
30
|
+
type: 'user',
|
|
31
|
+
name: 'Alice'
|
|
28
32
|
}
|
|
29
|
-
|
|
33
|
+
const result = await put(config, doc)
|
|
34
|
+
// result: { ok: true, id: 'doc-123', rev: '1-abc123' }
|
|
30
35
|
```
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
#### patch(config, id, properties)
|
|
38
|
+
Update specific properties of a document with retry mechanism.
|
|
39
|
+
- `config`: Object with:
|
|
40
|
+
- `couch`: URL string
|
|
41
|
+
- `retries`: Optional number of retry attempts (default: 5)
|
|
42
|
+
- `delay`: Optional milliseconds between retries (default: 1000)
|
|
43
|
+
- `id`: Document ID string
|
|
44
|
+
- `properties`: Object with properties to update
|
|
45
|
+
- Returns: Promise resolving to response with `ok`, `id`, `rev` properties
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
const config = {
|
|
49
|
+
couch: 'http://localhost:5984/mydb',
|
|
50
|
+
retries: 3,
|
|
51
|
+
delay: 500
|
|
52
|
+
}
|
|
53
|
+
const properties = {
|
|
54
|
+
name: 'Alice Smith',
|
|
55
|
+
updated: true
|
|
56
|
+
}
|
|
57
|
+
const result = await patch(config, 'doc-123', properties)
|
|
58
|
+
// result: { ok: true, id: 'doc-123', rev: '2-xyz789' }
|
|
33
59
|
```
|
|
34
|
-
import db from 'hide-a-bed'
|
|
35
|
-
import { doStuff } from './doStuff'
|
|
36
|
-
import { callSomeApi } from './api'
|
|
37
|
-
// the config object needs a couch url
|
|
38
|
-
const config = { couch: 'http://localhost:5984/mydb' }
|
|
39
|
-
// build up a service api for all your external calls that can be mocked/stubbed
|
|
40
|
-
const services = { db, callSomeApi }
|
|
41
|
-
const afterStuff = await doStuff(config, services, 'happy-doc-id')
|
|
42
60
|
|
|
43
|
-
|
|
61
|
+
#### remove(config, id)
|
|
62
|
+
Delete a document by ID.
|
|
63
|
+
- `config`: Object with `couch` URL string
|
|
64
|
+
- `id`: Document ID string to delete
|
|
65
|
+
- Returns: Promise resolving to response with `ok` and `rev` properties
|
|
44
66
|
|
|
45
|
-
|
|
67
|
+
```javascript
|
|
68
|
+
const config = { couch: 'http://localhost:5984/mydb' }
|
|
69
|
+
const result = await remove(config, 'doc-123')
|
|
70
|
+
// result: { ok: true, id: 'doc-123', rev: '2-def456' }
|
|
46
71
|
```
|
|
47
|
-
import { setup } from 'hide-a-bed-stub' // different package, since installed with --save-dev reduces space
|
|
48
|
-
import { doStuff } from './doStuff'
|
|
49
|
-
import { callSomeApiMock } from './test/mock/api'
|
|
50
|
-
// the config object needs a couch url, prove to yourself that its mocked with a fakeurl
|
|
51
|
-
const config = { couch: 'http://fakeurl:5984/mydb' }
|
|
52
72
|
|
|
53
|
-
|
|
54
|
-
|
|
73
|
+
### Bulk Operations
|
|
74
|
+
|
|
75
|
+
#### bulkSave(config, docs)
|
|
76
|
+
Save multiple documents in one request.
|
|
77
|
+
- `config`: Object with `couch` URL string
|
|
78
|
+
- `docs`: Array of document objects, each with `_id`
|
|
79
|
+
- Returns: Promise resolving to array of results with `ok`, `id`, `rev` for each doc
|
|
55
80
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
81
|
+
```javascript
|
|
82
|
+
const config = { couch: 'http://localhost:5984/mydb' }
|
|
83
|
+
const docs = [
|
|
84
|
+
{ _id: 'doc1', type: 'user', name: 'Alice' },
|
|
85
|
+
{ _id: 'doc2', type: 'user', name: 'Bob' }
|
|
86
|
+
]
|
|
87
|
+
const results = await bulkSave(config, docs)
|
|
88
|
+
// results: [
|
|
89
|
+
// { ok: true, id: 'doc1', rev: '1-abc123' },
|
|
90
|
+
// { ok: true, id: 'doc2', rev: '1-def456' }
|
|
91
|
+
// ]
|
|
92
|
+
```
|
|
59
93
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
94
|
+
#### bulkGet(config, ids)
|
|
95
|
+
Get multiple documents by ID.
|
|
96
|
+
- `config`: Object with `couch` URL string
|
|
97
|
+
- `ids`: Array of document ID strings
|
|
98
|
+
- Returns: Promise resolving to array of documents
|
|
64
99
|
|
|
100
|
+
```javascript
|
|
101
|
+
const config = { couch: 'http://localhost:5984/mydb' }
|
|
102
|
+
const ids = ['doc1', 'doc2']
|
|
103
|
+
const docs = await bulkGet(config, ids)
|
|
104
|
+
// docs: [
|
|
105
|
+
// { _id: 'doc1', _rev: '1-abc123', type: 'user', name: 'Alice' },
|
|
106
|
+
// { _id: 'doc2', _rev: '1-def456', type: 'user', name: 'Bob' }
|
|
107
|
+
// ]
|
|
65
108
|
```
|
|
66
109
|
|
|
67
|
-
|
|
68
|
-
|
|
110
|
+
#### bulkRemove(config, ids)
|
|
111
|
+
Delete multiple documents in one request.
|
|
112
|
+
- `config`: Object with `couch` URL string
|
|
113
|
+
- `ids`: Array of document ID strings to delete
|
|
114
|
+
- Returns: Promise resolving to array of results with `ok`, `id`, `rev` for each deletion
|
|
69
115
|
|
|
70
|
-
|
|
116
|
+
```javascript
|
|
117
|
+
const config = { couch: 'http://localhost:5984/mydb' }
|
|
118
|
+
const ids = ['doc1', 'doc2']
|
|
119
|
+
const results = await bulkRemove(config, ids)
|
|
120
|
+
// results: [
|
|
121
|
+
// { ok: true, id: 'doc1', rev: '2-ghi789' },
|
|
122
|
+
// { ok: true, id: 'doc2', rev: '2-jkl012' }
|
|
123
|
+
// ]
|
|
124
|
+
```
|
|
71
125
|
|
|
126
|
+
### View Queries
|
|
127
|
+
|
|
128
|
+
#### query(config, view, options)
|
|
129
|
+
Query a view with options.
|
|
130
|
+
- `config`: Object with `couch` URL string
|
|
131
|
+
- `view`: View path string (e.g. '_design/doc/_view/name')
|
|
132
|
+
- `options`: Optional object with query parameters:
|
|
133
|
+
- `startkey`: Start key for range
|
|
134
|
+
- `endkey`: End key for range
|
|
135
|
+
- `key`: Exact key match
|
|
136
|
+
- `descending`: Boolean to reverse sort
|
|
137
|
+
- `skip`: Number of results to skip
|
|
138
|
+
- `limit`: Max number of results
|
|
139
|
+
- `include_docs`: Boolean to include full docs
|
|
140
|
+
- `reduce`: Boolean to reduce results
|
|
141
|
+
- `group`: Boolean to group results
|
|
142
|
+
- `group_level`: Number for group level
|
|
143
|
+
- Returns: Promise resolving to response with `rows` array
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
const config = { couch: 'http://localhost:5984/mydb' }
|
|
147
|
+
const view = '_design/users/_view/by_name'
|
|
148
|
+
const options = {
|
|
149
|
+
startkey: 'A',
|
|
150
|
+
endkey: 'B',
|
|
151
|
+
include_docs: true,
|
|
152
|
+
limit: 10
|
|
153
|
+
}
|
|
154
|
+
const result = await query(config, view, options)
|
|
155
|
+
// result: {
|
|
156
|
+
// rows: [
|
|
157
|
+
// {
|
|
158
|
+
// id: 'doc1',
|
|
159
|
+
// key: 'Alice',
|
|
160
|
+
// value: 1,
|
|
161
|
+
// doc: { _id: 'doc1', name: 'Alice', type: 'user' }
|
|
162
|
+
// },
|
|
163
|
+
// // ... more rows
|
|
164
|
+
// ]
|
|
165
|
+
// }
|
|
166
|
+
```
|
|
72
167
|
|
|
73
168
|
|
package/impl/crud.mjs
CHANGED
|
@@ -12,11 +12,9 @@ const opts = {
|
|
|
12
12
|
export const get = CouchGet.implement(async (config, id) => {
|
|
13
13
|
const url = `${config.couch}/${id}`
|
|
14
14
|
const resp = await needle('get', url, opts)
|
|
15
|
+
if (resp.statusCode === 404) return null
|
|
15
16
|
const result = resp?.body || {}
|
|
16
|
-
|
|
17
|
-
if (resp.statusCode !== 200) {
|
|
18
|
-
throw new Error('not found')
|
|
19
|
-
}
|
|
17
|
+
if (resp.statusCode !== 200) { throw new Error(result.reason || 'failed') }
|
|
20
18
|
return result
|
|
21
19
|
})
|
|
22
20
|
|
package/impl/patch.mjs
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { get, put } from './crud.mjs'
|
|
2
|
+
import { CouchPatch } from '../schema/patch.mjs'
|
|
3
|
+
|
|
4
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
|
5
|
+
|
|
6
|
+
export const patch = CouchPatch.implement(async (config, id, properties) => {
|
|
7
|
+
const maxRetries = config.retries || 5
|
|
8
|
+
const delay = config.delay || 1000
|
|
9
|
+
let attempts = 0
|
|
10
|
+
|
|
11
|
+
while (attempts <= maxRetries) {
|
|
12
|
+
try {
|
|
13
|
+
const doc = await get(config, id)
|
|
14
|
+
const updatedDoc = { ...doc, ...properties }
|
|
15
|
+
const result = await put(config, updatedDoc)
|
|
16
|
+
|
|
17
|
+
// Check if the response indicates a conflict
|
|
18
|
+
if (result.ok) {
|
|
19
|
+
return result
|
|
20
|
+
}
|
|
21
|
+
// If not ok, treat as conflict and retry
|
|
22
|
+
attempts++
|
|
23
|
+
if (attempts > maxRetries) {
|
|
24
|
+
throw new Error(`Failed to patch after ${maxRetries} attempts`)
|
|
25
|
+
}
|
|
26
|
+
await sleep(delay)
|
|
27
|
+
} catch (err) {
|
|
28
|
+
// Handle other errors (network, etc)
|
|
29
|
+
attempts++
|
|
30
|
+
if (attempts > maxRetries) {
|
|
31
|
+
throw new Error(`Failed to patch after ${maxRetries} attempts: ${err.message}`)
|
|
32
|
+
}
|
|
33
|
+
await sleep(delay)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
})
|
package/index.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { bulkGet, bulkSave, bulkRemove } from './impl/bulk.mjs'
|
|
2
2
|
import { get, put } from './impl/crud.mjs'
|
|
3
|
+
import { patch } from './impl/patch.mjs'
|
|
3
4
|
import { query } from './impl/query.mjs'
|
|
4
5
|
import { queryStream } from './impl/stream.mjs'
|
|
5
6
|
import { BulkSave, BulkGet } from './schema/bulk.mjs'
|
|
6
7
|
import { CouchConfig } from './schema/config.mjs'
|
|
7
8
|
import { SimpleViewQuery, SimpleViewQueryResponse } from './schema/query.mjs'
|
|
9
|
+
import { PatchConfig, PatchDoc } from './schema/patch.mjs'
|
|
8
10
|
import { CouchDoc, CouchDocResponse, CouchPut, CouchGet } from './schema/crud.mjs'
|
|
9
11
|
|
|
10
12
|
const schema = {
|
|
@@ -16,8 +18,10 @@ const schema = {
|
|
|
16
18
|
CouchGet,
|
|
17
19
|
CouchPut,
|
|
18
20
|
CouchDoc,
|
|
19
|
-
CouchDocResponse
|
|
21
|
+
CouchDocResponse,
|
|
22
|
+
PatchConfig,
|
|
23
|
+
PatchDoc
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
export { get, put, bulkGet, bulkSave, bulkRemove, query, queryStream, schema }
|
|
26
|
+
export { get, put, patch, bulkGet, bulkSave, bulkRemove, query, queryStream, schema }
|
|
23
27
|
|
package/package.json
CHANGED
package/schema/bulk.mjs
CHANGED
|
@@ -22,3 +22,8 @@ export const BulkGet = z.function().args(
|
|
|
22
22
|
CouchConfig,
|
|
23
23
|
z.array(z.string().describe('the ids to get'))
|
|
24
24
|
)
|
|
25
|
+
|
|
26
|
+
export const BulkRemove = z.function().args(
|
|
27
|
+
CouchConfig,
|
|
28
|
+
z.array(z.string().describe('the ids to delete'))
|
|
29
|
+
).returns(z.promise(BulkSaveResponseSchema))
|
package/schema/crud.mjs
CHANGED
package/schema/patch.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { CouchConfig } from './config.mjs'
|
|
3
|
+
import { CouchDocResponse } from './crud.mjs'
|
|
4
|
+
|
|
5
|
+
export const PatchConfig = CouchConfig.extend({
|
|
6
|
+
retries: z.number().min(0).max(100).optional(),
|
|
7
|
+
delay: z.number().min(0).optional()
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export const PatchProperties = z.record(z.string(), z.any())
|
|
11
|
+
|
|
12
|
+
export const CouchPatch = z.function()
|
|
13
|
+
.args(
|
|
14
|
+
PatchConfig,
|
|
15
|
+
z.string().describe('the couch doc id'),
|
|
16
|
+
PatchProperties
|
|
17
|
+
)
|
|
18
|
+
.returns(z.promise(CouchDocResponse))
|