hide-a-bed 6.0.0 → 7.0.0-beta.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 +89 -28
- package/dist/cjs/index.cjs +888 -443
- package/dist/esm/index.mjs +883 -443
- package/eslint.config.js +6 -1
- package/impl/bindConfig.mts +30 -3
- package/impl/bulkGet.mts +50 -27
- package/impl/bulkRemove.mts +4 -2
- package/impl/bulkSave.mts +50 -28
- package/impl/get.mts +49 -40
- package/impl/getDBInfo.mts +26 -24
- package/impl/patch.mts +46 -42
- package/impl/put.mts +39 -21
- package/impl/query.mts +101 -81
- package/impl/remove.mts +33 -33
- package/impl/stream.mts +163 -102
- package/impl/sugar/watch.mts +165 -97
- package/impl/utils/errors.mts +261 -35
- package/impl/utils/fetch.mts +201 -0
- package/impl/utils/parseRows.mts +47 -6
- package/impl/utils/request.mts +22 -0
- package/impl/utils/response.mts +50 -0
- package/impl/utils/transactionErrors.mts +14 -8
- package/impl/utils/url.mts +21 -0
- package/index.mts +19 -2
- package/migration_guides/v7.md +353 -0
- package/package.json +4 -4
- package/schema/config.mts +17 -34
- package/schema/request.mts +36 -0
- package/schema/sugar/watch.mts +1 -1
- package/tsconfig.json +9 -1
- package/types/output/impl/bindConfig.d.mts +31 -149
- package/types/output/impl/bindConfig.d.mts.map +1 -1
- package/types/output/impl/bindConfig.test.d.mts +2 -0
- package/types/output/impl/bindConfig.test.d.mts.map +1 -0
- package/types/output/impl/bulkGet.d.mts +5 -5
- package/types/output/impl/bulkGet.d.mts.map +1 -1
- package/types/output/impl/bulkRemove.d.mts +4 -2
- package/types/output/impl/bulkRemove.d.mts.map +1 -1
- package/types/output/impl/bulkSave.d.mts +2 -2
- package/types/output/impl/bulkSave.d.mts.map +1 -1
- package/types/output/impl/get.d.mts +2 -2
- package/types/output/impl/get.d.mts.map +1 -1
- package/types/output/impl/getDBInfo.d.mts +1 -1
- package/types/output/impl/getDBInfo.d.mts.map +1 -1
- package/types/output/impl/patch.d.mts +8 -3
- package/types/output/impl/patch.d.mts.map +1 -1
- package/types/output/impl/put.d.mts.map +1 -1
- package/types/output/impl/query.d.mts +8 -23
- package/types/output/impl/query.d.mts.map +1 -1
- package/types/output/impl/remove.d.mts.map +1 -1
- package/types/output/impl/request-controls.test.d.mts +2 -0
- package/types/output/impl/request-controls.test.d.mts.map +1 -0
- package/types/output/impl/stream.d.mts +1 -1
- package/types/output/impl/stream.d.mts.map +1 -1
- package/types/output/impl/sugar/watch.d.mts +7 -5
- package/types/output/impl/sugar/watch.d.mts.map +1 -1
- package/types/output/impl/utils/errors.d.mts +84 -26
- package/types/output/impl/utils/errors.d.mts.map +1 -1
- package/types/output/impl/utils/fetch.d.mts +27 -0
- package/types/output/impl/utils/fetch.d.mts.map +1 -0
- package/types/output/impl/utils/fetch.test.d.mts +2 -0
- package/types/output/impl/utils/fetch.test.d.mts.map +1 -0
- package/types/output/impl/utils/parseRows.d.mts +3 -0
- package/types/output/impl/utils/parseRows.d.mts.map +1 -1
- package/types/output/impl/utils/request.d.mts +6 -0
- package/types/output/impl/utils/request.d.mts.map +1 -0
- package/types/output/impl/utils/response.d.mts +7 -0
- package/types/output/impl/utils/response.d.mts.map +1 -0
- package/types/output/impl/utils/response.test.d.mts +2 -0
- package/types/output/impl/utils/response.test.d.mts.map +1 -0
- package/types/output/impl/utils/trackedEmitter.test.d.mts +2 -0
- package/types/output/impl/utils/trackedEmitter.test.d.mts.map +1 -0
- package/types/output/impl/utils/transactionErrors.d.mts +5 -4
- package/types/output/impl/utils/transactionErrors.d.mts.map +1 -1
- package/types/output/impl/utils/transactionErrors.test.d.mts +2 -0
- package/types/output/impl/utils/transactionErrors.test.d.mts.map +1 -0
- package/types/output/impl/utils/url.d.mts +4 -0
- package/types/output/impl/utils/url.d.mts.map +1 -0
- package/types/output/impl/utils/url.test.d.mts +2 -0
- package/types/output/impl/utils/url.test.d.mts.map +1 -0
- package/types/output/index.d.mts +5 -2
- package/types/output/index.d.mts.map +1 -1
- package/types/output/schema/config.d.mts +13 -69
- package/types/output/schema/config.d.mts.map +1 -1
- package/types/output/schema/config.test.d.mts +2 -0
- package/types/output/schema/config.test.d.mts.map +1 -0
- package/types/output/schema/request.d.mts +10 -0
- package/types/output/schema/request.d.mts.map +1 -0
- package/types/output/schema/sugar/lock.test.d.mts +2 -0
- package/types/output/schema/sugar/lock.test.d.mts.map +1 -0
- package/types/output/schema/sugar/watch.d.mts +1 -1
- package/types/output/schema/sugar/watch.d.mts.map +1 -1
- package/types/output/schema/sugar/watch.test.d.mts +2 -0
- package/types/output/schema/sugar/watch.test.d.mts.map +1 -0
- package/impl/utils/mergeNeedleOpts.mts +0 -16
- package/schema/util.mts +0 -8
- package/types/output/impl/utils/mergeNeedleOpts.d.mts +0 -53
- package/types/output/impl/utils/mergeNeedleOpts.d.mts.map +0 -1
- package/types/output/schema/util.d.mts +0 -85
- package/types/output/schema/util.d.mts.map +0 -1
package/eslint.config.js
CHANGED
|
@@ -11,5 +11,10 @@ export default defineConfig([
|
|
|
11
11
|
extends: ['js/recommended'],
|
|
12
12
|
languageOptions: { globals: globals.browser }
|
|
13
13
|
},
|
|
14
|
-
tseslint.configs.recommended
|
|
14
|
+
tseslint.configs.recommended,
|
|
15
|
+
{
|
|
16
|
+
rules: {
|
|
17
|
+
'@typescript-eslint/no-unused-vars': ['error', { ignoreRestSiblings: true }]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
15
20
|
])
|
package/impl/bindConfig.mts
CHANGED
|
@@ -20,7 +20,34 @@ import { remove } from './remove.mts'
|
|
|
20
20
|
import { createLock, removeLock } from './sugar/lock.mts'
|
|
21
21
|
import { watchDocs } from './sugar/watch.mts'
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
type BoundConfigMethod<T> = T extends (...args: infer Args) => infer Result
|
|
24
|
+
? Args extends [unknown, ...infer Rest]
|
|
25
|
+
? (...args: Rest) => Result
|
|
26
|
+
: never
|
|
27
|
+
: never
|
|
28
|
+
|
|
29
|
+
type BoundMethods = {
|
|
30
|
+
bulkGet: BulkGetBound
|
|
31
|
+
bulkGetDictionary: BulkGetDictionaryBound
|
|
32
|
+
get: GetBound
|
|
33
|
+
getAtRev: GetAtRevBound
|
|
34
|
+
query: QueryBound
|
|
35
|
+
bulkRemove: BoundConfigMethod<typeof bulkRemove>
|
|
36
|
+
bulkRemoveMap: BoundConfigMethod<typeof bulkRemoveMap>
|
|
37
|
+
bulkSave: BoundConfigMethod<typeof bulkSave>
|
|
38
|
+
bulkSaveTransaction: BoundConfigMethod<typeof bulkSaveTransaction>
|
|
39
|
+
getDBInfo: BoundConfigMethod<typeof getDBInfo>
|
|
40
|
+
patch: BoundConfigMethod<typeof patch>
|
|
41
|
+
patchDangerously: BoundConfigMethod<typeof patchDangerously>
|
|
42
|
+
put: BoundConfigMethod<typeof put>
|
|
43
|
+
queryStream: BoundConfigMethod<typeof queryStream>
|
|
44
|
+
remove: BoundConfigMethod<typeof remove>
|
|
45
|
+
createLock: BoundConfigMethod<typeof createLock>
|
|
46
|
+
removeLock: BoundConfigMethod<typeof removeLock>
|
|
47
|
+
watchDocs: BoundConfigMethod<typeof watchDocs>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type BoundInstance = BoundMethods & {
|
|
24
51
|
options(overrides: Partial<z.input<typeof CouchConfig>>): BoundInstance
|
|
25
52
|
}
|
|
26
53
|
|
|
@@ -81,7 +108,7 @@ export function getBoundWithRetry<
|
|
|
81
108
|
* @param config The CouchDB configuration
|
|
82
109
|
* @returns An object with CouchDB operations bound to the provided configuration
|
|
83
110
|
*/
|
|
84
|
-
function doBind(config: CouchConfig) {
|
|
111
|
+
function doBind(config: CouchConfig): BoundMethods {
|
|
85
112
|
// Default retry options
|
|
86
113
|
const retryOptions = {
|
|
87
114
|
maxRetries: config.maxRetries ?? 10,
|
|
@@ -90,7 +117,7 @@ function doBind(config: CouchConfig) {
|
|
|
90
117
|
}
|
|
91
118
|
|
|
92
119
|
// Create the object without the config property first
|
|
93
|
-
const result = {
|
|
120
|
+
const result: BoundMethods = {
|
|
94
121
|
/**
|
|
95
122
|
* These functions use overloaded signatures when bound.
|
|
96
123
|
* To preserve the overloads we need dedicated Bound types
|
package/impl/bulkGet.mts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import needle from 'needle'
|
|
2
1
|
import { CouchConfig, type CouchConfigInput } from '../schema/config.mts'
|
|
3
2
|
import { createLogger } from './utils/logger.mts'
|
|
4
|
-
import {
|
|
5
|
-
import { RetryableError } from './utils/errors.mts'
|
|
3
|
+
import { RetryableError, createResponseError } from './utils/errors.mts'
|
|
6
4
|
import {
|
|
7
5
|
ViewQueryResponse,
|
|
8
6
|
type ViewQueryResponseValidated,
|
|
@@ -11,6 +9,15 @@ import {
|
|
|
11
9
|
} from '../schema/couch/couch.output.schema.ts'
|
|
12
10
|
import type { StandardSchemaV1 } from '../types/standard-schema.ts'
|
|
13
11
|
import { parseRows, type OnInvalidDocAction } from './utils/parseRows.mts'
|
|
12
|
+
import { fetchCouchJson } from './utils/fetch.mts'
|
|
13
|
+
import { isSuccessStatusCode } from './utils/response.mts'
|
|
14
|
+
import { createCouchPathUrl } from './utils/url.mts'
|
|
15
|
+
|
|
16
|
+
type BulkGetBody = {
|
|
17
|
+
error?: string
|
|
18
|
+
reason?: string
|
|
19
|
+
rows?: unknown[]
|
|
20
|
+
} & Record<string, unknown>
|
|
14
21
|
|
|
15
22
|
export type BulkGetResponse<DocSchema extends StandardSchemaV1 = StandardSchemaV1<CouchDoc>> =
|
|
16
23
|
ViewQueryResponseValidated<
|
|
@@ -39,13 +46,13 @@ export type BulkGetOptions<DocSchema extends StandardSchemaV1> = {
|
|
|
39
46
|
* @returns The raw response body from CouchDB
|
|
40
47
|
*
|
|
41
48
|
* @throws {RetryableError} When a retryable HTTP status code is encountered or no response is received.
|
|
42
|
-
* @throws {
|
|
49
|
+
* @throws {OperationError} When CouchDB returns a non-retryable request-level failure.
|
|
43
50
|
*/
|
|
44
51
|
async function executeBulkGet(
|
|
45
52
|
_config: CouchConfigInput,
|
|
46
53
|
ids: Array<string | undefined>,
|
|
47
54
|
includeDocs: boolean
|
|
48
|
-
) {
|
|
55
|
+
): Promise<BulkGetBody | undefined> {
|
|
49
56
|
const configParseResult = CouchConfig.safeParse(_config)
|
|
50
57
|
const logger = createLogger(_config)
|
|
51
58
|
logger.info(`Starting bulk get for ${ids.length} documents`)
|
|
@@ -56,30 +63,40 @@ async function executeBulkGet(
|
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
const config = configParseResult.data
|
|
59
|
-
const url =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
json: true,
|
|
63
|
-
headers: {
|
|
64
|
-
'Content-Type': 'application/json'
|
|
65
|
-
}
|
|
66
|
+
const url = createCouchPathUrl('_all_docs', config.couch)
|
|
67
|
+
if (includeDocs) {
|
|
68
|
+
url.searchParams.append('include_docs', 'true')
|
|
66
69
|
}
|
|
67
|
-
const
|
|
70
|
+
const payload = { keys: ids }
|
|
68
71
|
|
|
69
72
|
try {
|
|
70
|
-
const resp = await
|
|
73
|
+
const resp = await fetchCouchJson<BulkGetBody>({
|
|
74
|
+
auth: config.auth,
|
|
75
|
+
method: 'POST',
|
|
76
|
+
operation: 'request',
|
|
77
|
+
request: config.request,
|
|
78
|
+
url,
|
|
79
|
+
body: payload
|
|
80
|
+
})
|
|
71
81
|
if (RetryableError.isRetryableStatusCode(resp.statusCode)) {
|
|
72
82
|
logger.warn(`Retryable status code received: ${resp.statusCode}`)
|
|
73
|
-
throw new RetryableError('
|
|
83
|
+
throw new RetryableError('Bulk get failed', resp.statusCode, {
|
|
84
|
+
operation: 'request'
|
|
85
|
+
})
|
|
74
86
|
}
|
|
75
|
-
if (resp.statusCode
|
|
87
|
+
if (!isSuccessStatusCode('bulkGet', resp.statusCode)) {
|
|
76
88
|
logger.error(`Unexpected status code: ${resp.statusCode}`)
|
|
77
|
-
throw
|
|
89
|
+
throw createResponseError({
|
|
90
|
+
body: resp.body,
|
|
91
|
+
defaultMessage: 'Bulk get failed',
|
|
92
|
+
operation: 'request',
|
|
93
|
+
statusCode: resp.statusCode
|
|
94
|
+
})
|
|
78
95
|
}
|
|
79
96
|
return resp.body
|
|
80
97
|
} catch (err) {
|
|
81
98
|
logger.error('Network error during bulk get:', err)
|
|
82
|
-
RetryableError.handleNetworkError(err)
|
|
99
|
+
RetryableError.handleNetworkError(err, 'request')
|
|
83
100
|
}
|
|
84
101
|
}
|
|
85
102
|
|
|
@@ -95,8 +112,8 @@ async function executeBulkGet(
|
|
|
95
112
|
* @returns The bulk get response with rows optionally validated against the supplied document schema.
|
|
96
113
|
*
|
|
97
114
|
* @throws {RetryableError} When a retryable HTTP status code is encountered or no response is received.
|
|
98
|
-
* @throws {
|
|
99
|
-
* @throws {
|
|
115
|
+
* @throws {ValidationError} When returned documents fail schema validation.
|
|
116
|
+
* @throws {OperationError} When CouchDB returns a non-retryable request-level failure.
|
|
100
117
|
*/
|
|
101
118
|
async function _bulkGetWithOptions<DocSchema extends StandardSchemaV1 = typeof CouchDoc>(
|
|
102
119
|
config: CouchConfigInput,
|
|
@@ -107,16 +124,22 @@ async function _bulkGetWithOptions<DocSchema extends StandardSchemaV1 = typeof C
|
|
|
107
124
|
const body = await executeBulkGet(config, ids, includeDocs)
|
|
108
125
|
|
|
109
126
|
if (!body) {
|
|
110
|
-
throw new RetryableError('
|
|
127
|
+
throw new RetryableError('Bulk get failed', 503, { operation: 'request' })
|
|
111
128
|
}
|
|
112
129
|
|
|
113
130
|
if (body.error) {
|
|
114
|
-
throw
|
|
131
|
+
throw createResponseError({
|
|
132
|
+
body,
|
|
133
|
+
defaultMessage: 'Bulk get failed',
|
|
134
|
+
operation: 'request'
|
|
135
|
+
})
|
|
115
136
|
}
|
|
116
137
|
|
|
117
138
|
const docSchema = options.validate?.docSchema || CouchDoc
|
|
118
139
|
const rows = await parseRows(body.rows, {
|
|
140
|
+
defaultMessage: 'Bulk get failed',
|
|
119
141
|
onInvalidDoc: options.validate?.onInvalidDoc,
|
|
142
|
+
operation: 'request',
|
|
120
143
|
docSchema
|
|
121
144
|
})
|
|
122
145
|
|
|
@@ -144,8 +167,8 @@ async function _bulkGetWithOptions<DocSchema extends StandardSchemaV1 = typeof C
|
|
|
144
167
|
* @returns The bulk get response with rows optionally validated against the supplied document schema.
|
|
145
168
|
*
|
|
146
169
|
* @throws {RetryableError} When a retryable HTTP status code is encountered or no response is received.
|
|
147
|
-
* @throws {
|
|
148
|
-
* @throws {
|
|
170
|
+
* @throws {ValidationError} When returned documents fail schema validation.
|
|
171
|
+
* @throws {OperationError} When CouchDB returns a non-retryable request-level failure.
|
|
149
172
|
*/
|
|
150
173
|
export async function bulkGet<DocSchema extends StandardSchemaV1 = typeof CouchDoc>(
|
|
151
174
|
config: CouchConfigInput,
|
|
@@ -196,7 +219,7 @@ export type BulkGetDictionaryResult<
|
|
|
196
219
|
/**
|
|
197
220
|
* Bulk get documents by IDs and return a dictionary of found and not found documents.
|
|
198
221
|
*
|
|
199
|
-
* @template DocSchema - Schema used to validate each returned document, if provided. Note: if a document is found and it fails validation this will throw a
|
|
222
|
+
* @template DocSchema - Schema used to validate each returned document, if provided. Note: if a document is found and it fails validation this will throw a ValidationError.
|
|
200
223
|
*
|
|
201
224
|
* @param config - CouchDB configuration data that is validated before use.
|
|
202
225
|
* @param ids - Array of document IDs to retrieve.
|
|
@@ -205,8 +228,8 @@ export type BulkGetDictionaryResult<
|
|
|
205
228
|
* @returns An object containing found documents and not found rows.
|
|
206
229
|
*
|
|
207
230
|
* @throws {RetryableError} When a retryable HTTP status code is encountered or no response is received.
|
|
208
|
-
* @throws {
|
|
209
|
-
* @throws {
|
|
231
|
+
* @throws {ValidationError} When returned documents fail schema validation.
|
|
232
|
+
* @throws {OperationError} When CouchDB returns a non-retryable request-level failure.
|
|
210
233
|
*/
|
|
211
234
|
export async function bulkGetDictionary<DocSchema extends StandardSchemaV1 = typeof CouchDoc>(
|
|
212
235
|
config: CouchConfigInput,
|
package/impl/bulkRemove.mts
CHANGED
|
@@ -27,7 +27,8 @@ import { CouchConfig, type CouchConfigInput } from '../schema/config.mts'
|
|
|
27
27
|
* console.log(results);
|
|
28
28
|
* ```
|
|
29
29
|
*
|
|
30
|
-
* @throws
|
|
30
|
+
* @throws {RetryableError} When the bulk request fails with a retryable transport or HTTP error.
|
|
31
|
+
* @throws {OperationError} When CouchDB returns a non-retryable request-level failure.
|
|
31
32
|
*/
|
|
32
33
|
export const bulkRemove = async (configInput: CouchConfigInput, ids: string[]) => {
|
|
33
34
|
const config = CouchConfig.parse(configInput)
|
|
@@ -71,7 +72,8 @@ export const bulkRemove = async (configInput: CouchConfigInput, ids: string[]) =
|
|
|
71
72
|
* console.log(results);
|
|
72
73
|
* ```
|
|
73
74
|
*
|
|
74
|
-
* @throws
|
|
75
|
+
* @throws {RetryableError} When a request-level transport or retryable HTTP failure occurs.
|
|
76
|
+
* @throws {OperationError} When a request fails in a non-retryable way.
|
|
75
77
|
*/
|
|
76
78
|
export const bulkRemoveMap = async (configInput: CouchConfigInput, ids: string[]) => {
|
|
77
79
|
const config = CouchConfig.parse(configInput)
|
package/impl/bulkSave.mts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import needle from 'needle'
|
|
2
1
|
import { createLogger } from './utils/logger.mts'
|
|
3
|
-
import { mergeNeedleOpts } from './utils/mergeNeedleOpts.mts'
|
|
4
2
|
import { bulkGetDictionary } from './bulkGet.mts'
|
|
5
3
|
import { setupEmitter } from './utils/trackedEmitter.mts'
|
|
6
4
|
import {
|
|
@@ -14,10 +12,18 @@ import {
|
|
|
14
12
|
CouchDoc,
|
|
15
13
|
type CouchDocInput
|
|
16
14
|
} from '../schema/couch/couch.output.schema.ts'
|
|
17
|
-
import type
|
|
18
|
-
import {
|
|
15
|
+
import { CouchConfig, type CouchConfigInput } from '../schema/config.mts'
|
|
16
|
+
import {
|
|
17
|
+
ConflictError,
|
|
18
|
+
OperationError,
|
|
19
|
+
RetryableError,
|
|
20
|
+
createResponseError
|
|
21
|
+
} from './utils/errors.mts'
|
|
19
22
|
import { withRetry } from './retry.mts'
|
|
20
23
|
import { put } from './put.mts'
|
|
24
|
+
import { fetchCouchJson } from './utils/fetch.mts'
|
|
25
|
+
import { isSuccessStatusCode } from './utils/response.mts'
|
|
26
|
+
import { createCouchPathUrl } from './utils/url.mts'
|
|
21
27
|
|
|
22
28
|
/**
|
|
23
29
|
* Bulk saves documents to CouchDB using the _bulk_docs endpoint.
|
|
@@ -30,44 +36,54 @@ import { put } from './put.mts'
|
|
|
30
36
|
* @returns {Promise<BulkSaveResponse>} - The response from CouchDB after the bulk save operation.
|
|
31
37
|
*
|
|
32
38
|
* @throws {RetryableError} When a retryable HTTP status code is encountered or no response is received.
|
|
33
|
-
* @throws {
|
|
39
|
+
* @throws {OperationError} When bulk save input is invalid or CouchDB returns a non-retryable request-level failure.
|
|
34
40
|
*/
|
|
35
41
|
export const bulkSave = async (config: CouchConfigInput, docs: CouchDocInput[]) => {
|
|
36
|
-
const
|
|
42
|
+
const parsedConfig = CouchConfig.parse(config)
|
|
43
|
+
const logger = createLogger(parsedConfig)
|
|
37
44
|
|
|
38
45
|
if (docs == null || !docs.length) {
|
|
39
46
|
logger.error('bulkSave called with no docs')
|
|
40
|
-
throw new
|
|
47
|
+
throw new OperationError('Bulk save requires at least one document', {
|
|
48
|
+
operation: 'request'
|
|
49
|
+
})
|
|
41
50
|
}
|
|
42
51
|
|
|
43
52
|
logger.info(`Starting bulk save of ${docs.length} documents`)
|
|
44
|
-
const url =
|
|
53
|
+
const url = createCouchPathUrl('_bulk_docs', parsedConfig.couch)
|
|
45
54
|
const body = { docs }
|
|
46
|
-
const opts = {
|
|
47
|
-
json: true,
|
|
48
|
-
headers: {
|
|
49
|
-
'Content-Type': 'application/json'
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
const mergedOpts = mergeNeedleOpts(config, opts)
|
|
53
55
|
let resp
|
|
54
56
|
try {
|
|
55
|
-
resp = await
|
|
57
|
+
resp = await fetchCouchJson({
|
|
58
|
+
auth: parsedConfig.auth,
|
|
59
|
+
method: 'POST',
|
|
60
|
+
operation: 'request',
|
|
61
|
+
request: parsedConfig.request,
|
|
62
|
+
url,
|
|
63
|
+
body
|
|
64
|
+
})
|
|
56
65
|
} catch (err) {
|
|
57
66
|
logger.error('Network error during bulk save:', err)
|
|
58
|
-
RetryableError.handleNetworkError(err)
|
|
67
|
+
RetryableError.handleNetworkError(err, 'request')
|
|
59
68
|
}
|
|
60
69
|
if (!resp) {
|
|
61
70
|
logger.error('No response received from bulk save request')
|
|
62
|
-
throw new RetryableError('
|
|
71
|
+
throw new RetryableError('Bulk save failed', 503, { operation: 'request' })
|
|
63
72
|
}
|
|
64
73
|
if (RetryableError.isRetryableStatusCode(resp.statusCode)) {
|
|
65
74
|
logger.warn(`Retryable status code received: ${resp.statusCode}`)
|
|
66
|
-
throw new RetryableError('
|
|
75
|
+
throw new RetryableError('Bulk save failed', resp.statusCode, {
|
|
76
|
+
operation: 'request'
|
|
77
|
+
})
|
|
67
78
|
}
|
|
68
|
-
if (resp.statusCode
|
|
79
|
+
if (!isSuccessStatusCode('bulkSave', resp.statusCode)) {
|
|
69
80
|
logger.error(`Unexpected status code: ${resp.statusCode}`)
|
|
70
|
-
throw
|
|
81
|
+
throw createResponseError({
|
|
82
|
+
body: resp.body,
|
|
83
|
+
defaultMessage: 'Bulk save failed',
|
|
84
|
+
operation: 'request',
|
|
85
|
+
statusCode: resp.statusCode
|
|
86
|
+
})
|
|
71
87
|
}
|
|
72
88
|
const results = resp?.body || []
|
|
73
89
|
return BulkSaveResponse.parse(results)
|
|
@@ -151,18 +167,24 @@ export const bulkSaveTransaction = async (
|
|
|
151
167
|
}
|
|
152
168
|
|
|
153
169
|
// Save transaction document
|
|
154
|
-
let transactionResponse
|
|
170
|
+
let transactionResponse
|
|
171
|
+
try {
|
|
172
|
+
transactionResponse = await _put(transactionDoc)
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (error instanceof ConflictError) {
|
|
175
|
+
throw new TransactionSetupError('Failed to create transaction document', {
|
|
176
|
+
error: error.couchError,
|
|
177
|
+
response: error
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
throw error
|
|
182
|
+
}
|
|
155
183
|
logger.debug('Transaction document created:', transactionDoc, transactionResponse)
|
|
156
184
|
await emitter.emit('transaction-created', {
|
|
157
185
|
transactionResponse,
|
|
158
186
|
txnDoc: transactionDoc
|
|
159
187
|
})
|
|
160
|
-
if (transactionResponse.error) {
|
|
161
|
-
throw new TransactionSetupError('Failed to create transaction document', {
|
|
162
|
-
error: transactionResponse.error,
|
|
163
|
-
response: transactionResponse
|
|
164
|
-
})
|
|
165
|
-
}
|
|
166
188
|
|
|
167
189
|
// Get current revisions of all documents
|
|
168
190
|
const existingDocs = await bulkGetDictionary(
|
package/impl/get.mts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
import needle from 'needle'
|
|
2
1
|
import { z } from 'zod'
|
|
3
|
-
import type { CouchConfigInput } from '../schema/config.mts'
|
|
4
2
|
import { createLogger } from './utils/logger.mts'
|
|
5
|
-
import {
|
|
6
|
-
|
|
3
|
+
import {
|
|
4
|
+
RetryableError,
|
|
5
|
+
NotFoundError,
|
|
6
|
+
ValidationError,
|
|
7
|
+
createResponseError
|
|
8
|
+
} from './utils/errors.mts'
|
|
7
9
|
import type { StandardSchemaV1 } from '../types/standard-schema.ts'
|
|
8
10
|
import { CouchDoc } from '../schema/couch/couch.output.schema.ts'
|
|
11
|
+
import { fetchCouchJson } from './utils/fetch.mts'
|
|
12
|
+
import { CouchConfig, type CouchConfigInput } from '../schema/config.mts'
|
|
13
|
+
import { isSuccessStatusCode } from './utils/response.mts'
|
|
14
|
+
import { createCouchDocUrl } from './utils/url.mts'
|
|
9
15
|
|
|
10
16
|
export type GetOptions<DocSchema extends StandardSchemaV1> = {
|
|
11
17
|
validate?: {
|
|
@@ -26,7 +32,7 @@ const ValidSchema = z.custom(
|
|
|
26
32
|
}
|
|
27
33
|
)
|
|
28
34
|
|
|
29
|
-
export const CouchGetOptions = z.
|
|
35
|
+
export const CouchGetOptions = z.strictObject({
|
|
30
36
|
rev: z.string().optional().describe('the couch doc revision'),
|
|
31
37
|
validate: z
|
|
32
38
|
.object({
|
|
@@ -37,74 +43,74 @@ export const CouchGetOptions = z.object({
|
|
|
37
43
|
})
|
|
38
44
|
|
|
39
45
|
async function _getWithOptions<DocSchema extends StandardSchemaV1>(
|
|
40
|
-
|
|
46
|
+
configInput: CouchConfigInput,
|
|
41
47
|
id: string,
|
|
42
48
|
options: InternalGetOptions<DocSchema>
|
|
43
49
|
): Promise<StandardSchemaV1.InferOutput<DocSchema> | null> {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
validate: options.validate
|
|
47
|
-
})
|
|
50
|
+
const config = CouchConfig.parse(configInput)
|
|
51
|
+
const parsedOptions = CouchGetOptions.parse(options)
|
|
48
52
|
|
|
49
53
|
const logger = createLogger(config)
|
|
50
54
|
const rev = parsedOptions.rev
|
|
51
|
-
const
|
|
52
|
-
const url =
|
|
55
|
+
const operation = rev ? 'getAtRev' : 'get'
|
|
56
|
+
const url = createCouchDocUrl(id, config.couch)
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
headers: {
|
|
57
|
-
'Content-Type': 'application/json'
|
|
58
|
-
}
|
|
58
|
+
if (rev) {
|
|
59
|
+
url.searchParams.set('rev', rev)
|
|
59
60
|
}
|
|
60
|
-
|
|
61
|
-
const requestOptions = mergeNeedleOpts(config, httpOptions)
|
|
62
61
|
logger.info(`Getting document with id: ${id}, rev ${rev ?? 'latest'}`)
|
|
63
62
|
|
|
64
63
|
try {
|
|
65
|
-
const resp = await
|
|
64
|
+
const resp = await fetchCouchJson({
|
|
65
|
+
auth: config.auth,
|
|
66
|
+
method: 'GET',
|
|
67
|
+
operation,
|
|
68
|
+
request: config.request,
|
|
69
|
+
url
|
|
70
|
+
})
|
|
66
71
|
if (!resp) {
|
|
67
72
|
logger.error('No response received from get request')
|
|
68
|
-
throw new RetryableError('
|
|
73
|
+
throw new RetryableError('Request failed', 503, { operation })
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
const body = resp.body ?? null
|
|
72
77
|
|
|
73
78
|
if (resp.statusCode === 404) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
throw new NotFoundError(id, reason)
|
|
79
|
+
logger.warn(`Document not found: ${id}, rev ${rev ?? 'latest'}`)
|
|
80
|
+
if (config.throwOnGetNotFound === false) {
|
|
81
|
+
return null
|
|
78
82
|
}
|
|
79
|
-
|
|
80
|
-
logger.debug(`Document not found (returning undefined): ${id}, rev ${rev ?? 'latest'}`)
|
|
81
|
-
return null
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (RetryableError.isRetryableStatusCode(resp.statusCode)) {
|
|
85
|
-
const reason = typeof body?.reason === 'string' ? body.reason : 'retryable error'
|
|
86
|
-
logger.warn(`Retryable status code received: ${resp.statusCode}`)
|
|
87
|
-
throw new RetryableError(reason, resp.statusCode)
|
|
83
|
+
throw new NotFoundError(id, { operation, statusCode: resp.statusCode })
|
|
88
84
|
}
|
|
89
85
|
|
|
90
|
-
if (resp.statusCode
|
|
91
|
-
const reason = typeof body?.reason === 'string' ? body.reason : 'failed'
|
|
86
|
+
if (!isSuccessStatusCode('documentRead', resp.statusCode)) {
|
|
92
87
|
logger.error(`Unexpected status code: ${resp.statusCode}`)
|
|
93
|
-
throw
|
|
88
|
+
throw createResponseError({
|
|
89
|
+
body,
|
|
90
|
+
defaultMessage: 'Failed to fetch document',
|
|
91
|
+
docId: id,
|
|
92
|
+
operation,
|
|
93
|
+
statusCode: resp.statusCode
|
|
94
|
+
})
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
const docSchema = (parsedOptions.validate?.docSchema ?? CouchDoc) as DocSchema
|
|
97
98
|
const typedDoc = await docSchema['~standard'].validate(body)
|
|
98
99
|
|
|
99
100
|
if (typedDoc.issues) {
|
|
100
|
-
throw
|
|
101
|
+
throw new ValidationError({
|
|
102
|
+
docId: id,
|
|
103
|
+
issues: typedDoc.issues,
|
|
104
|
+
message: 'Document validation failed',
|
|
105
|
+
operation
|
|
106
|
+
})
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
logger.info(`Successfully retrieved document: ${id}, rev ${rev ?? 'latest'}`)
|
|
104
110
|
return typedDoc.value
|
|
105
111
|
} catch (err) {
|
|
106
112
|
logger.error('Error during get operation:', err)
|
|
107
|
-
RetryableError.handleNetworkError(err)
|
|
113
|
+
RetryableError.handleNetworkError(err, operation)
|
|
108
114
|
}
|
|
109
115
|
}
|
|
110
116
|
|
|
@@ -127,7 +133,10 @@ export async function getAtRev<DocSchema extends StandardSchemaV1>(
|
|
|
127
133
|
rev: string,
|
|
128
134
|
options?: GetOptions<DocSchema>
|
|
129
135
|
): Promise<StandardSchemaV1.InferOutput<DocSchema> | null> {
|
|
130
|
-
return _getWithOptions<DocSchema>(config, id, {
|
|
136
|
+
return _getWithOptions<DocSchema>(config, id, {
|
|
137
|
+
...options,
|
|
138
|
+
rev
|
|
139
|
+
})
|
|
131
140
|
}
|
|
132
141
|
|
|
133
142
|
export type GetAtRevBound = <DocSchema extends StandardSchemaV1 = typeof CouchDoc>(
|
package/impl/getDBInfo.mts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { RetryableError } from './utils/errors.mts'
|
|
1
|
+
import { RetryableError, createResponseError } from './utils/errors.mts'
|
|
3
2
|
import { createLogger } from './utils/logger.mts'
|
|
4
|
-
import { mergeNeedleOpts } from './utils/mergeNeedleOpts.mts'
|
|
5
3
|
import { CouchConfig, type CouchConfigInput } from '../schema/config.mts'
|
|
6
4
|
import { CouchDBInfo } from '../schema/couch/couch.output.schema.ts'
|
|
5
|
+
import { fetchCouchJson } from './utils/fetch.mts'
|
|
6
|
+
import { isSuccessStatusCode } from './utils/response.mts'
|
|
7
|
+
import { createCouchDbUrl } from './utils/url.mts'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Fetches and returns CouchDB database information.
|
|
@@ -13,7 +14,7 @@ import { CouchDBInfo } from '../schema/couch/couch.output.schema.ts'
|
|
|
13
14
|
* @param configInput - The CouchDB configuration input.
|
|
14
15
|
* @returns A promise that resolves to the CouchDB database information.
|
|
15
16
|
* @throws {RetryableError} `RetryableError` If a retryable error occurs during the request.
|
|
16
|
-
* @throws {
|
|
17
|
+
* @throws {OperationError} `OperationError` For other non-retryable response failures.
|
|
17
18
|
*
|
|
18
19
|
* @example
|
|
19
20
|
* ```ts
|
|
@@ -33,35 +34,36 @@ import { CouchDBInfo } from '../schema/couch/couch.output.schema.ts'
|
|
|
33
34
|
export const getDBInfo = async (configInput: CouchConfigInput) => {
|
|
34
35
|
const config = CouchConfig.parse(configInput)
|
|
35
36
|
const logger = createLogger(config)
|
|
36
|
-
const url =
|
|
37
|
+
const url = createCouchDbUrl(config.couch)
|
|
37
38
|
|
|
38
|
-
let resp
|
|
39
|
+
let resp
|
|
39
40
|
try {
|
|
40
|
-
resp = await
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
resp = await fetchCouchJson({
|
|
42
|
+
auth: config.auth,
|
|
43
|
+
method: 'GET',
|
|
44
|
+
operation: 'getDBInfo',
|
|
45
|
+
request: config.request,
|
|
46
|
+
url
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
if (!isSuccessStatusCode('database', resp.statusCode)) {
|
|
50
|
+
logger.error(`Non-success status code received: ${resp.statusCode}`)
|
|
51
|
+
throw createResponseError({
|
|
52
|
+
body: resp.body,
|
|
53
|
+
defaultMessage: 'Failed to fetch database info',
|
|
54
|
+
operation: 'getDBInfo',
|
|
55
|
+
statusCode: resp.statusCode
|
|
48
56
|
})
|
|
49
|
-
|
|
57
|
+
}
|
|
50
58
|
} catch (err) {
|
|
51
59
|
logger.error('Error during get operation:', err)
|
|
52
|
-
RetryableError.handleNetworkError(err)
|
|
60
|
+
RetryableError.handleNetworkError(err, 'getDBInfo')
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
if (!resp) {
|
|
56
64
|
logger.error('No response received from get request')
|
|
57
|
-
throw new RetryableError('
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const result = resp.body
|
|
61
|
-
if (RetryableError.isRetryableStatusCode(resp.statusCode)) {
|
|
62
|
-
logger.warn(`Retryable status code received: ${resp.statusCode}`)
|
|
63
|
-
throw new RetryableError(result.reason ?? 'retryable error', resp.statusCode)
|
|
65
|
+
throw new RetryableError('Failed to fetch database info', 503, { operation: 'getDBInfo' })
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
return CouchDBInfo.parse(
|
|
68
|
+
return CouchDBInfo.parse(resp.body)
|
|
67
69
|
}
|