hide-a-bed 6.0.0-beta.0 → 6.0.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/migration_guides/v6.md +70 -0
- package/package.json +4 -1
- package/docs/.nojekyll +0 -1
- package/docs/assets/hierarchy.js +0 -1
- package/docs/assets/highlight.css +0 -113
- package/docs/assets/icons.js +0 -18
- package/docs/assets/icons.svg +0 -1
- package/docs/assets/main.js +0 -60
- package/docs/assets/navigation.js +0 -1
- package/docs/assets/search.js +0 -1
- package/docs/assets/style.css +0 -1633
- package/docs/classes/QueryBuilder.html +0 -42
- package/docs/functions/bindConfig.html +0 -4
- package/docs/functions/bulkGet.html +0 -14
- package/docs/functions/bulkGetDictionary.html +0 -10
- package/docs/functions/bulkRemove.html +0 -12
- package/docs/functions/bulkRemoveMap.html +0 -11
- package/docs/functions/bulkSave.html +0 -10
- package/docs/functions/bulkSaveTransaction.html +0 -23
- package/docs/functions/createLock.html +0 -7
- package/docs/functions/createQuery.html +0 -1
- package/docs/functions/get.html +0 -1
- package/docs/functions/getAtRev.html +0 -1
- package/docs/functions/getDBInfo.html +0 -10
- package/docs/functions/patch.html +0 -8
- package/docs/functions/patchDangerously.html +0 -9
- package/docs/functions/put.html +0 -1
- package/docs/functions/query.html +0 -15
- package/docs/functions/queryStream.html +0 -6
- package/docs/functions/remove.html +0 -1
- package/docs/functions/removeLock.html +0 -6
- package/docs/functions/watchDocs.html +0 -9
- package/docs/functions/withRetry.html +0 -6
- package/docs/hierarchy.html +0 -1
- package/docs/index.html +0 -483
- package/docs/interfaces/NetworkError.html +0 -6
- package/docs/interfaces/NotFoundError.html +0 -10
- package/docs/interfaces/RetryOptions.html +0 -10
- package/docs/interfaces/RetryableError.html +0 -10
- package/docs/interfaces/StandardSchemaV1.FailureResult.html +0 -4
- package/docs/interfaces/StandardSchemaV1.Issue.html +0 -6
- package/docs/interfaces/StandardSchemaV1.Options.html +0 -3
- package/docs/interfaces/StandardSchemaV1.PathSegment.html +0 -4
- package/docs/interfaces/StandardSchemaV1.Props.html +0 -10
- package/docs/interfaces/StandardSchemaV1.SuccessResult.html +0 -6
- package/docs/interfaces/StandardSchemaV1.Types.html +0 -6
- package/docs/interfaces/StandardSchemaV1.html +0 -4
- package/docs/modules/StandardSchemaV1.html +0 -1
- package/docs/modules.html +0 -1
- package/docs/types/BoundInstance.html +0 -1
- package/docs/types/BulkGetBound.html +0 -2
- package/docs/types/BulkGetDictionaryBound.html +0 -1
- package/docs/types/BulkGetDictionaryOptions.html +0 -2
- package/docs/types/BulkGetDictionaryResult.html +0 -3
- package/docs/types/BulkGetOptions.html +0 -3
- package/docs/types/BulkGetResponse.html +0 -1
- package/docs/types/CouchConfig-1.html +0 -1
- package/docs/types/CouchConfig.html +0 -1
- package/docs/types/CouchConfigInput.html +0 -1
- package/docs/types/CouchDoc-1.html +0 -1
- package/docs/types/CouchDoc.html +0 -2
- package/docs/types/CouchDocInput.html +0 -2
- package/docs/types/GetAtRevBound.html +0 -1
- package/docs/types/GetBound.html +0 -1
- package/docs/types/GetOptions.html +0 -2
- package/docs/types/LockDoc-1.html +0 -1
- package/docs/types/LockDoc.html +0 -1
- package/docs/types/LockOptions-1.html +0 -1
- package/docs/types/LockOptions.html +0 -1
- package/docs/types/LockOptionsInput.html +0 -1
- package/docs/types/OnInvalidDocAction.html +0 -1
- package/docs/types/OnRow.html +0 -1
- package/docs/types/QueryBound.html +0 -1
- package/docs/types/SimpleViewOptions-1.html +0 -1
- package/docs/types/SimpleViewOptions.html +0 -1
- package/docs/types/StandardSchemaV1.InferInput.html +0 -2
- package/docs/types/StandardSchemaV1.InferOutput.html +0 -2
- package/docs/types/StandardSchemaV1.Result.html +0 -2
- package/docs/types/ViewQueryResponse-1.html +0 -1
- package/docs/types/ViewQueryResponse.html +0 -2
- package/docs/types/ViewQueryResponseValidated.html +0 -2
- package/docs/types/ViewRow-1.html +0 -1
- package/docs/types/ViewRow.html +0 -2
- package/docs/types/ViewRowValidated.html +0 -7
- package/docs/types/ViewString.html +0 -1
- package/docs/types/WatchOptionsInput.html +0 -1
- package/docs/types/WatchOptionsSchema-1.html +0 -1
- package/docs/types/WatchOptionsSchema.html +0 -1
- package/impl/bulkGet.test.mts +0 -159
- package/impl/bulkRemove.test.mts +0 -102
- package/impl/bulkSave.test.mts +0 -319
- package/impl/get.test.mts +0 -114
- package/impl/getDBInfo.test.mts +0 -62
- package/impl/patch.test.mts +0 -142
- package/impl/put.test.mts +0 -114
- package/impl/query.test.mts +0 -280
- package/impl/remove.test.mts +0 -82
- package/impl/retry.test.mts +0 -77
- package/impl/stream.test.mts +0 -205
- package/impl/sugar/lock.test.mts +0 -113
- package/impl/sugar/watch.test.mts +0 -155
- package/impl/utils/errors.test.mts +0 -58
- package/impl/utils/logger.test.mts +0 -129
- package/impl/utils/parseRows.test.mts +0 -183
- package/impl/utils/queryBuilder.test.mts +0 -83
- package/impl/utils/queryString.test.mts +0 -53
- package/index.test.mts +0 -415
package/impl/put.test.mts
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import test, { suite } from 'node:test'
|
|
3
|
-
import needle from 'needle'
|
|
4
|
-
import type { CouchConfigInput } from '../schema/config.mts'
|
|
5
|
-
import { put } from './put.mts'
|
|
6
|
-
import { RetryableError } from './utils/errors.mts'
|
|
7
|
-
import { TEST_DB_URL } from '../test/setup-db.mts'
|
|
8
|
-
|
|
9
|
-
const baseConfig: CouchConfigInput = {
|
|
10
|
-
couch: TEST_DB_URL
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
type DocBody = Record<string, unknown>
|
|
14
|
-
|
|
15
|
-
async function getDoc(id: string) {
|
|
16
|
-
return needle('get', `${TEST_DB_URL}/${id}`, null, { json: true })
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async function saveDoc(id: string, body: DocBody) {
|
|
20
|
-
const response = await needle('put', `${TEST_DB_URL}/${id}`, { _id: id, ...body }, { json: true })
|
|
21
|
-
if (response.statusCode !== 201 && response.statusCode !== 200) {
|
|
22
|
-
throw new Error(`Failed to save document ${id}: ${response.statusCode}`)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return response.body as { rev: string }
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
suite('put', () => {
|
|
29
|
-
test('rejects invalid config arguments', async () => {
|
|
30
|
-
await assert.rejects(async () => {
|
|
31
|
-
// @ts-expect-error testing invalid config
|
|
32
|
-
await put({ couch: DB_URL, unsupported: true }, { _id: 'bad-config-doc' })
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
test('propagates retryable network failures', async () => {
|
|
37
|
-
const offlineConfig: CouchConfigInput = {
|
|
38
|
-
couch: 'http://localhost:6555/offline-put-test'
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
await assert.rejects(
|
|
42
|
-
() => put(offlineConfig, { _id: 'offline-doc', kind: 'offline' }),
|
|
43
|
-
(err: unknown) => err instanceof RetryableError && err.statusCode === 503
|
|
44
|
-
)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test('integration with pouchdb-server', async t => {
|
|
48
|
-
let initialRev: string | undefined
|
|
49
|
-
const put_doc_id = `put-doc-${Date.now()}`
|
|
50
|
-
|
|
51
|
-
await t.test('creates documents via PUT', async () => {
|
|
52
|
-
const result = await put(baseConfig, {
|
|
53
|
-
_id: put_doc_id,
|
|
54
|
-
type: 'integration',
|
|
55
|
-
count: 1
|
|
56
|
-
})
|
|
57
|
-
assert.ok(result)
|
|
58
|
-
assert.strictEqual(result.ok, true)
|
|
59
|
-
assert.strictEqual(result.id, put_doc_id)
|
|
60
|
-
assert.strictEqual(result.statusCode, 201)
|
|
61
|
-
assert.ok(typeof result.rev === 'string')
|
|
62
|
-
initialRev = result.rev
|
|
63
|
-
|
|
64
|
-
const { statusCode, body } = await getDoc(put_doc_id)
|
|
65
|
-
assert.strictEqual(statusCode, 200)
|
|
66
|
-
assert.strictEqual(body?.type, 'integration')
|
|
67
|
-
assert.strictEqual(body?.count, 1)
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
await t.test('updates documents when revision supplied', async () => {
|
|
71
|
-
if (!initialRev) throw new Error('Expected initial revision to be captured')
|
|
72
|
-
|
|
73
|
-
const updateResult = await put(baseConfig, {
|
|
74
|
-
_id: put_doc_id,
|
|
75
|
-
_rev: initialRev,
|
|
76
|
-
type: 'integration',
|
|
77
|
-
count: 2
|
|
78
|
-
})
|
|
79
|
-
assert.ok(updateResult)
|
|
80
|
-
assert.strictEqual(updateResult.ok, true)
|
|
81
|
-
assert.strictEqual(updateResult.statusCode, 201)
|
|
82
|
-
assert.ok(typeof updateResult.rev === 'string')
|
|
83
|
-
|
|
84
|
-
const { body } = await getDoc(put_doc_id)
|
|
85
|
-
assert.strictEqual(body?.count, 2)
|
|
86
|
-
initialRev = updateResult.rev
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
await t.test('reports conflicts when revision is stale', async () => {
|
|
90
|
-
if (!initialRev) throw new Error('Expected revision to be captured')
|
|
91
|
-
const staleRev = initialRev
|
|
92
|
-
const latest = await saveDoc(put_doc_id, {
|
|
93
|
-
_rev: staleRev,
|
|
94
|
-
type: 'integration',
|
|
95
|
-
count: 3
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
const result = await put(baseConfig, {
|
|
99
|
-
_id: put_doc_id,
|
|
100
|
-
_rev: staleRev,
|
|
101
|
-
type: 'integration',
|
|
102
|
-
count: 4
|
|
103
|
-
})
|
|
104
|
-
assert.ok(result)
|
|
105
|
-
assert.strictEqual(result.ok, false)
|
|
106
|
-
assert.strictEqual(result.error, 'conflict')
|
|
107
|
-
assert.strictEqual(result.statusCode, 409)
|
|
108
|
-
|
|
109
|
-
const { body } = await getDoc(put_doc_id)
|
|
110
|
-
assert.strictEqual(body?.count, 3)
|
|
111
|
-
initialRev = latest.rev
|
|
112
|
-
})
|
|
113
|
-
})
|
|
114
|
-
})
|
package/impl/query.test.mts
DELETED
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import test, { suite } from 'node:test'
|
|
3
|
-
import needle from 'needle'
|
|
4
|
-
import { randomUUID } from 'node:crypto'
|
|
5
|
-
import { setTimeout as delay } from 'node:timers/promises'
|
|
6
|
-
import { z } from 'zod'
|
|
7
|
-
|
|
8
|
-
import type { CouchConfigInput } from '../schema/config.mts'
|
|
9
|
-
import { TEST_DB_URL } from '../test/setup-db.mts'
|
|
10
|
-
import { query } from './query.mts'
|
|
11
|
-
import { RetryableError } from './utils/errors.mts'
|
|
12
|
-
|
|
13
|
-
const config: CouchConfigInput = {
|
|
14
|
-
couch: TEST_DB_URL
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async function putDoc(doc: Record<string, unknown> & { _id: string }) {
|
|
18
|
-
await needle('put', `${TEST_DB_URL}/${doc._id}`, doc, { json: true })
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function putDesignDoc(id: string, viewName: string, mapFn: string) {
|
|
22
|
-
await needle(
|
|
23
|
-
'put',
|
|
24
|
-
`${TEST_DB_URL}/_design/${id}`,
|
|
25
|
-
{
|
|
26
|
-
views: {
|
|
27
|
-
[viewName]: {
|
|
28
|
-
map: mapFn
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
{ json: true }
|
|
33
|
-
)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function eventually<T>(
|
|
37
|
-
fn: () => Promise<T>,
|
|
38
|
-
predicate: (value: T) => boolean,
|
|
39
|
-
attempts = 10,
|
|
40
|
-
waitMs = 100
|
|
41
|
-
): Promise<T> {
|
|
42
|
-
let lastValue: T | undefined
|
|
43
|
-
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
44
|
-
lastValue = await fn()
|
|
45
|
-
if (predicate(lastValue)) return lastValue
|
|
46
|
-
await delay(waitMs)
|
|
47
|
-
}
|
|
48
|
-
return lastValue!
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
suite('query', () => {
|
|
52
|
-
test('returns rows with include_docs', async () => {
|
|
53
|
-
const designId = `query-view-${randomUUID()}`
|
|
54
|
-
const viewName = 'byCategory'
|
|
55
|
-
const tag = `query-suite-${randomUUID()}`
|
|
56
|
-
await putDesignDoc(
|
|
57
|
-
designId,
|
|
58
|
-
viewName,
|
|
59
|
-
`function(doc) { if (doc.tag !== '${tag}') return; emit(doc.category, doc.count); }`
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
const matchingDoc = {
|
|
63
|
-
_id: `doc-${randomUUID()}`,
|
|
64
|
-
tag,
|
|
65
|
-
category: 'keep',
|
|
66
|
-
count: 42
|
|
67
|
-
}
|
|
68
|
-
const otherDoc = {
|
|
69
|
-
_id: `doc-${randomUUID()}`,
|
|
70
|
-
tag,
|
|
71
|
-
category: 'skip',
|
|
72
|
-
count: 1
|
|
73
|
-
}
|
|
74
|
-
const unrelatedDoc = {
|
|
75
|
-
_id: `doc-${randomUUID()}`,
|
|
76
|
-
tag: 'other',
|
|
77
|
-
category: 'keep',
|
|
78
|
-
count: 100
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
await putDoc(matchingDoc)
|
|
82
|
-
await putDoc(otherDoc)
|
|
83
|
-
await putDoc(unrelatedDoc)
|
|
84
|
-
|
|
85
|
-
const response = await eventually(
|
|
86
|
-
() =>
|
|
87
|
-
query(config, `_design/${designId}/_view/${viewName}`, {
|
|
88
|
-
include_docs: true,
|
|
89
|
-
key: matchingDoc.category
|
|
90
|
-
}),
|
|
91
|
-
({ rows }) => rows?.length === 1
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
if (!response.rows) {
|
|
95
|
-
throw new Error('Expected rows in response')
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
assert.strictEqual(response.rows[0].key, matchingDoc.category)
|
|
99
|
-
assert.strictEqual(response.rows[0].value, matchingDoc.count)
|
|
100
|
-
assert.strictEqual(response.rows[0].doc?._id, matchingDoc._id)
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
test('validates rows when schemas provided', async () => {
|
|
104
|
-
const designId = `query-validate-${randomUUID()}`
|
|
105
|
-
const viewName = 'byPlayer'
|
|
106
|
-
const tag = `query-suite-${randomUUID()}`
|
|
107
|
-
await putDesignDoc(
|
|
108
|
-
designId,
|
|
109
|
-
viewName,
|
|
110
|
-
`function(doc) { if (doc.tag !== '${tag}') return; emit(doc.player, doc.score); }`
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
const doc = { _id: `doc-${randomUUID()}`, tag, player: 'alpha', score: 7 }
|
|
114
|
-
await putDoc(doc)
|
|
115
|
-
|
|
116
|
-
const response = await eventually(
|
|
117
|
-
() =>
|
|
118
|
-
query(config, `_design/${designId}/_view/${viewName}`, {
|
|
119
|
-
include_docs: true,
|
|
120
|
-
key: doc.player,
|
|
121
|
-
validate: {
|
|
122
|
-
docSchema: z.looseObject({
|
|
123
|
-
_id: z.string(),
|
|
124
|
-
tag: z.string(),
|
|
125
|
-
player: z.string(),
|
|
126
|
-
score: z.number()
|
|
127
|
-
}),
|
|
128
|
-
keySchema: z.string(),
|
|
129
|
-
valueSchema: z.number()
|
|
130
|
-
}
|
|
131
|
-
}),
|
|
132
|
-
({ rows }) => rows.length === 1
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
assert.strictEqual(response.rows[0]?.value, doc.score)
|
|
136
|
-
assert.strictEqual(response.rows[0]?.doc?.player, doc.player)
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
test('rejects when validation fails', async () => {
|
|
140
|
-
const designId = `query-invalid-${randomUUID()}`
|
|
141
|
-
const viewName = 'byPlayer'
|
|
142
|
-
const tag = `query-suite-${randomUUID()}`
|
|
143
|
-
await putDesignDoc(
|
|
144
|
-
designId,
|
|
145
|
-
viewName,
|
|
146
|
-
`function(doc) { if (doc.tag !== '${tag}') return; emit(doc.player, doc.score); }`
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
const validDoc = {
|
|
150
|
-
_id: `doc-${randomUUID()}`,
|
|
151
|
-
tag,
|
|
152
|
-
player: 'valid',
|
|
153
|
-
score: 3
|
|
154
|
-
}
|
|
155
|
-
const invalidDoc = {
|
|
156
|
-
_id: `doc-${randomUUID()}`,
|
|
157
|
-
tag,
|
|
158
|
-
player: 'invalid',
|
|
159
|
-
score: 'nope'
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
await putDoc(validDoc)
|
|
163
|
-
await putDoc(invalidDoc)
|
|
164
|
-
|
|
165
|
-
await eventually(
|
|
166
|
-
() =>
|
|
167
|
-
query(config, `_design/${designId}/_view/${viewName}`, {
|
|
168
|
-
key: validDoc.player
|
|
169
|
-
}),
|
|
170
|
-
({ rows }) => rows?.length === 1
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
await assert.rejects(async () => {
|
|
174
|
-
return query(config, `_design/${designId}/_view/${viewName}`, {
|
|
175
|
-
validate: {
|
|
176
|
-
valueSchema: z.number()
|
|
177
|
-
}
|
|
178
|
-
})
|
|
179
|
-
})
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
test('skips invalid documents when onInvalidDoc=skip', async () => {
|
|
183
|
-
const designId = `query-skip-${randomUUID()}`
|
|
184
|
-
const viewName = 'byPlayer'
|
|
185
|
-
const tag = `query-suite-${randomUUID()}`
|
|
186
|
-
await putDesignDoc(
|
|
187
|
-
designId,
|
|
188
|
-
viewName,
|
|
189
|
-
`function(doc) { if (doc.tag !== '${tag}') return; emit(doc.player, doc.score); }`
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
const validDoc = {
|
|
193
|
-
_id: `doc-${randomUUID()}`,
|
|
194
|
-
tag,
|
|
195
|
-
player: 'valid',
|
|
196
|
-
score: 5
|
|
197
|
-
}
|
|
198
|
-
const invalidDoc = {
|
|
199
|
-
_id: `doc-${randomUUID()}`,
|
|
200
|
-
tag,
|
|
201
|
-
player: 'invalid',
|
|
202
|
-
score: 'nope'
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
await putDoc(validDoc)
|
|
206
|
-
await putDoc(invalidDoc)
|
|
207
|
-
|
|
208
|
-
const response = await eventually(
|
|
209
|
-
() =>
|
|
210
|
-
query(config, `_design/${designId}/_view/${viewName}`, {
|
|
211
|
-
include_docs: true,
|
|
212
|
-
validate: {
|
|
213
|
-
docSchema: z.looseObject({
|
|
214
|
-
_id: z.string(),
|
|
215
|
-
tag: z.string(),
|
|
216
|
-
player: z.string(),
|
|
217
|
-
score: z.number()
|
|
218
|
-
}),
|
|
219
|
-
keySchema: z.string(),
|
|
220
|
-
valueSchema: z.number(),
|
|
221
|
-
onInvalidDoc: 'skip'
|
|
222
|
-
}
|
|
223
|
-
}),
|
|
224
|
-
({ rows }) => rows.length === 1
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
assert.strictEqual(response.rows[0]?.doc?.player, validDoc.player)
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
test('posts payload when keys exceed URL limit', async () => {
|
|
231
|
-
const designId = `query-post-${randomUUID()}`
|
|
232
|
-
const viewName = 'byPlayer'
|
|
233
|
-
const tag = `query-suite-${randomUUID()}`
|
|
234
|
-
await putDesignDoc(
|
|
235
|
-
designId,
|
|
236
|
-
viewName,
|
|
237
|
-
`function(doc) { if (doc.tag !== '${tag}') return; emit(doc.player, doc.score); }`
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
const targetDoc = {
|
|
241
|
-
_id: `doc-${randomUUID()}`,
|
|
242
|
-
tag,
|
|
243
|
-
player: 'target',
|
|
244
|
-
score: 11
|
|
245
|
-
}
|
|
246
|
-
await putDoc(targetDoc)
|
|
247
|
-
|
|
248
|
-
await eventually(
|
|
249
|
-
() =>
|
|
250
|
-
query(config, `_design/${designId}/_view/${viewName}`, {
|
|
251
|
-
key: targetDoc.player
|
|
252
|
-
}),
|
|
253
|
-
({ rows }) => rows?.length === 1
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
const bulkKeys = Array.from({ length: 400 }, (_, index) => `missing-${index}-${randomUUID()}`)
|
|
257
|
-
bulkKeys.push(targetDoc.player)
|
|
258
|
-
|
|
259
|
-
const response = await query(config, `_design/${designId}/_view/${viewName}`, {
|
|
260
|
-
include_docs: true,
|
|
261
|
-
keys: bulkKeys
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
if (!response.rows) throw new Error('Expected rows in response')
|
|
265
|
-
assert.strictEqual(response.rows.length, 1)
|
|
266
|
-
assert.strictEqual(response.rows[0]?.key, targetDoc.player)
|
|
267
|
-
assert.strictEqual(response.rows[0]?.doc?._id, targetDoc._id)
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
test('throws RetryableError on network failure', async () => {
|
|
271
|
-
const offlineConfig: CouchConfigInput = {
|
|
272
|
-
couch: 'http://localhost:6553/offline-db'
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
await assert.rejects(
|
|
276
|
-
() => query(offlineConfig, '_all_docs', {}),
|
|
277
|
-
(err: unknown) => err instanceof RetryableError && err.statusCode === 503
|
|
278
|
-
)
|
|
279
|
-
})
|
|
280
|
-
})
|
package/impl/remove.test.mts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import test, { suite } from 'node:test'
|
|
3
|
-
import needle from 'needle'
|
|
4
|
-
import type { CouchConfigInput } from '../schema/config.mts'
|
|
5
|
-
import { remove } from './remove.mts'
|
|
6
|
-
import { RetryableError } from './utils/errors.mts'
|
|
7
|
-
import { TEST_DB_URL } from '../test/setup-db.mts'
|
|
8
|
-
|
|
9
|
-
const baseConfig: CouchConfigInput = {
|
|
10
|
-
couch: TEST_DB_URL
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
type DocBody = Record<string, unknown>
|
|
14
|
-
|
|
15
|
-
async function saveDoc(id: string, body: DocBody) {
|
|
16
|
-
const response = await needle(
|
|
17
|
-
'put',
|
|
18
|
-
`${TEST_DB_URL}/${id}`,
|
|
19
|
-
{
|
|
20
|
-
_id: id,
|
|
21
|
-
...body
|
|
22
|
-
},
|
|
23
|
-
{ json: true }
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
if (response.statusCode !== 201 && response.statusCode !== 200) {
|
|
27
|
-
throw new Error(`Failed to save document ${id}: ${response.statusCode}`)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return response.body as { rev: string }
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function getDoc(id: string) {
|
|
34
|
-
return needle('get', `${TEST_DB_URL}/${id}`, null, { json: true })
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
suite('remove', () => {
|
|
38
|
-
test('it should throw if provided config is invalid', async () => {
|
|
39
|
-
await assert.rejects(async () => {
|
|
40
|
-
await remove(
|
|
41
|
-
// @ts-expect-error testing invalid config
|
|
42
|
-
{ couch: DB_URL, useConsoleLogger: true, unexpected: true },
|
|
43
|
-
'doc-invalid-config',
|
|
44
|
-
'1-invalid'
|
|
45
|
-
)
|
|
46
|
-
})
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
test('integration with pouchdb-server', async t => {
|
|
50
|
-
await t.test('removes an existing document', async () => {
|
|
51
|
-
const remove_doc_id = `remove-doc-1-${Date.now()}`
|
|
52
|
-
const { rev } = await saveDoc(remove_doc_id, { kind: 'test', count: 1 })
|
|
53
|
-
|
|
54
|
-
const result = await remove(baseConfig, remove_doc_id, rev)
|
|
55
|
-
assert.strictEqual(result.ok, true)
|
|
56
|
-
assert.strictEqual(result.id, remove_doc_id)
|
|
57
|
-
assert.strictEqual(result.statusCode, 200)
|
|
58
|
-
|
|
59
|
-
const { statusCode, body } = await getDoc(remove_doc_id)
|
|
60
|
-
assert.strictEqual(statusCode, 404)
|
|
61
|
-
assert.strictEqual(body?.error, 'not_found')
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
await t.test('returns not found metadata when document is missing', async () => {
|
|
65
|
-
const result = await remove(baseConfig, 'remove-doc-missing', '1-missing')
|
|
66
|
-
assert.strictEqual(result.ok, false)
|
|
67
|
-
assert.strictEqual(result.error, 'not_found')
|
|
68
|
-
assert.strictEqual(result.statusCode, 404)
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
await t.test('propagates retryable network errors', async () => {
|
|
72
|
-
const offlineConfig: CouchConfigInput = {
|
|
73
|
-
couch: 'http://localhost:6553/offline-remove-db'
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
await assert.rejects(
|
|
77
|
-
() => remove(offlineConfig, 'remove-doc-network', '1-offline'),
|
|
78
|
-
(err: unknown) => err instanceof RetryableError && err.statusCode === 503
|
|
79
|
-
)
|
|
80
|
-
})
|
|
81
|
-
})
|
|
82
|
-
})
|
package/impl/retry.test.mts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import test, { suite } from 'node:test'
|
|
3
|
-
import { withRetry } from './retry.mts'
|
|
4
|
-
import { RetryableError } from './utils/errors.mts'
|
|
5
|
-
|
|
6
|
-
suite('withRetry', () => {
|
|
7
|
-
test('resolves when the wrapped function succeeds without retries', async () => {
|
|
8
|
-
let count = 0
|
|
9
|
-
const fn = async (value: string) => {
|
|
10
|
-
count += 1
|
|
11
|
-
return value
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const wrapped = withRetry(fn)
|
|
15
|
-
const result = await wrapped('ok')
|
|
16
|
-
|
|
17
|
-
assert.strictEqual(result, 'ok')
|
|
18
|
-
assert.strictEqual(count, 1)
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
test('retries on RetryableError until success', async () => {
|
|
22
|
-
let attempts = 0
|
|
23
|
-
const fn = async () => {
|
|
24
|
-
attempts += 1
|
|
25
|
-
if (attempts < 3) {
|
|
26
|
-
throw new RetryableError('temporary', 503)
|
|
27
|
-
}
|
|
28
|
-
return 'done'
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const wrapped = withRetry(fn, { initialDelay: 0, maxDelay: 0 })
|
|
32
|
-
const result = await wrapped()
|
|
33
|
-
|
|
34
|
-
assert.strictEqual(result, 'done')
|
|
35
|
-
assert.strictEqual(attempts, 3)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
test('propagates non retryable errors immediately', async () => {
|
|
39
|
-
let attempts = 0
|
|
40
|
-
const fn = async () => {
|
|
41
|
-
attempts += 1
|
|
42
|
-
throw new Error('fatal')
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const wrapped = withRetry(fn, { initialDelay: 0, maxDelay: 0 })
|
|
46
|
-
|
|
47
|
-
await assert.rejects(
|
|
48
|
-
() => wrapped(),
|
|
49
|
-
(err: unknown) => {
|
|
50
|
-
return err instanceof Error && !(err instanceof RetryableError) && err.message === 'fatal'
|
|
51
|
-
}
|
|
52
|
-
)
|
|
53
|
-
assert.strictEqual(attempts, 1)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
test('throws after exceeding the maximum retries', async () => {
|
|
57
|
-
let attempts = 0
|
|
58
|
-
const fn = async () => {
|
|
59
|
-
attempts += 1
|
|
60
|
-
throw new RetryableError('still failing', 503)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const wrapped = withRetry(fn, {
|
|
64
|
-
maxRetries: 2,
|
|
65
|
-
initialDelay: 0,
|
|
66
|
-
maxDelay: 0
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
await assert.rejects(
|
|
70
|
-
() => wrapped(),
|
|
71
|
-
(err: unknown) => {
|
|
72
|
-
return err instanceof RetryableError && err.message === 'still failing'
|
|
73
|
-
}
|
|
74
|
-
)
|
|
75
|
-
assert.strictEqual(attempts, 3)
|
|
76
|
-
})
|
|
77
|
-
})
|