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 CHANGED
@@ -1,73 +1,168 @@
1
- hide-a-bed
2
- -----------
3
-
4
- A simple way to abstract couchdb, and make your interface to the database testable.
1
+ API
2
+ -------------
5
3
 
6
- Install
7
- -----
4
+ ### Document Operations
8
5
 
9
- There are two packages, one for runtime that contains the real implementations and schema, and the other contains the stubs for tests.
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
- Code that uses some example db apis
18
- ```
19
- export function doStuff (config, services, id) {
20
- const doc = await services.db.get(config, id)
21
- const apiResult = services.callSomeApi(config, doc.userName)
22
- const query = {
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
- Using doStuff, in a real env, connecting to a real couch
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
- Mocking out the calls in a test, never connects to the network
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
- // we import or design docs that we will need for the db
54
- import userThingsDesignDoc from './ddocs/userThingsDDoc.js'
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
- test('doStuff works in stub mode', async t => {
57
- // we have to setup the db with the design docs that are required
58
- const db = await setup([userThingsDesignDoc])
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
- // build up a service api with all your fake endpoints
61
- const services = { db, callSomeApi: callSomeApiMock }
62
- const afterStuff = await doStuff(config, services, 'happy-doc-id')
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
- Below are all the couch apis available
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
- __TODO__
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
- result.statusCode = resp.statusCode
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hide-a-bed",
3
- "version": "1.2.0",
3
+ "version": "2.1.0",
4
4
  "description": "An abstraction over couchdb calls that includes easy mock/stubs with pouchdb",
5
5
  "main": "index.mjs",
6
6
  "scripts": {
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
@@ -22,4 +22,4 @@ export const CouchPut = z.function().args(
22
22
  export const CouchGet = z.function().args(
23
23
  CouchConfig,
24
24
  z.string().describe('the couch doc id')
25
- ).returns(z.promise(CouchDoc))
25
+ ).returns(z.promise(CouchDoc.nullable()))
@@ -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))