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/bulkSave.test.mts
DELETED
|
@@ -1,319 +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 { bulkSave, bulkSaveTransaction } from './bulkSave.mts'
|
|
6
|
-
import { RetryableError } from './utils/errors.mts'
|
|
7
|
-
import {
|
|
8
|
-
TransactionRollbackError,
|
|
9
|
-
TransactionVersionConflictError
|
|
10
|
-
} from './utils/transactionErrors.mts'
|
|
11
|
-
import { TEST_DB_URL } from '../test/setup-db.mts'
|
|
12
|
-
|
|
13
|
-
const baseConfig: CouchConfigInput = {
|
|
14
|
-
couch: TEST_DB_URL,
|
|
15
|
-
useConsoleLogger: true
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const transactionBaseConfig: CouchConfigInput = {
|
|
19
|
-
couch: TEST_DB_URL,
|
|
20
|
-
bindWithRetry: false,
|
|
21
|
-
useConsoleLogger: true
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
type EventRecord = { event: string; payload: unknown }
|
|
25
|
-
|
|
26
|
-
function createTestEmitter() {
|
|
27
|
-
const events: EventRecord[] = []
|
|
28
|
-
const handlers = new Map<string, Array<(payload: unknown) => Promise<void> | void>>()
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
events,
|
|
32
|
-
on(event: string, handler: (payload: unknown) => Promise<void> | void) {
|
|
33
|
-
const list = handlers.get(event) ?? []
|
|
34
|
-
list.push(handler)
|
|
35
|
-
handlers.set(event, list)
|
|
36
|
-
},
|
|
37
|
-
async emit(event: string, payload: unknown) {
|
|
38
|
-
events.push({ event, payload })
|
|
39
|
-
const list = handlers.get(event)
|
|
40
|
-
if (!list) return
|
|
41
|
-
for (const handler of list) {
|
|
42
|
-
await handler(payload)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function saveDoc(dbUrl: string, id: string, body: Record<string, unknown>) {
|
|
49
|
-
const response = await needle('put', `${dbUrl}/${id}`, { _id: id, ...body }, { json: true })
|
|
50
|
-
|
|
51
|
-
if (response.statusCode !== 201 && response.statusCode !== 200) {
|
|
52
|
-
throw new Error(`Failed to save document ${id}: ${response.statusCode}`)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return response.body as { rev: string }
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async function getDocFrom(dbUrl: string, id: string) {
|
|
59
|
-
return needle('get', `${dbUrl}/${id}`, null, { json: true })
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function getDoc(id: string) {
|
|
63
|
-
return getDocFrom(TEST_DB_URL, id)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
suite('bulkSave', () => {
|
|
67
|
-
test('rejects invalid config arguments', async () => {
|
|
68
|
-
await assert.rejects(async () => {
|
|
69
|
-
// @ts-expect-error intentionally passing unsupported option
|
|
70
|
-
await bulkSave({ couch: TEST_DB_URL, unsupported: true }, [
|
|
71
|
-
{ _id: 'bad-config-doc', count: 1 }
|
|
72
|
-
])
|
|
73
|
-
})
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
test('throws error if called with no docs', async () => {
|
|
77
|
-
await assert.rejects(async () => {
|
|
78
|
-
// @ts-expect-error testing no docs
|
|
79
|
-
await bulkSave(baseConfig, null)
|
|
80
|
-
})
|
|
81
|
-
await assert.rejects(async () => {
|
|
82
|
-
await bulkSave(baseConfig, [])
|
|
83
|
-
})
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
test('propagates retryable network failures', async () => {
|
|
87
|
-
const offlineConfig: CouchConfigInput = {
|
|
88
|
-
couch: 'http://localhost:6554/offline-bulk-save'
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
await assert.rejects(
|
|
92
|
-
() => bulkSave(offlineConfig, [{ _id: 'offline-doc', count: 1 }]),
|
|
93
|
-
(err: unknown) => err instanceof RetryableError && err.statusCode === 503
|
|
94
|
-
)
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
test('integration with pouchdb-server', async t => {
|
|
98
|
-
let docTwoInitialRev: string | undefined
|
|
99
|
-
const docs = [
|
|
100
|
-
{ _id: `bulk-save-doc-1-${Date.now()}`, type: 'integration', count: 1 },
|
|
101
|
-
{ _id: `bulk-save-doc-2-${Date.now()}`, type: 'integration', count: 2 }
|
|
102
|
-
]
|
|
103
|
-
|
|
104
|
-
await t.test('creates documents via _bulk_docs', async () => {
|
|
105
|
-
const results = await bulkSave(baseConfig, docs)
|
|
106
|
-
assert.strictEqual(results.length, 2)
|
|
107
|
-
const [first, second] = results
|
|
108
|
-
assert.ok(first)
|
|
109
|
-
assert.strictEqual(first.id, docs[0]._id)
|
|
110
|
-
assert.strictEqual(first.ok, true)
|
|
111
|
-
assert.ok(second)
|
|
112
|
-
assert.strictEqual(second.id, docs[1]._id)
|
|
113
|
-
assert.strictEqual(second.ok, true)
|
|
114
|
-
|
|
115
|
-
docTwoInitialRev = second.rev ?? undefined
|
|
116
|
-
assert.ok(typeof docTwoInitialRev === 'string')
|
|
117
|
-
|
|
118
|
-
const { statusCode, body } = await getDoc(docs[0]._id)
|
|
119
|
-
assert.strictEqual(statusCode, 200)
|
|
120
|
-
assert.strictEqual(body?.type, 'integration')
|
|
121
|
-
assert.strictEqual(body?.count, 1)
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
await t.test('updates documents when revision supplied', async () => {
|
|
125
|
-
const current = await getDoc(docs[1]._id)
|
|
126
|
-
assert.strictEqual(current.statusCode, 200)
|
|
127
|
-
const updateResults = await bulkSave(baseConfig, [
|
|
128
|
-
{
|
|
129
|
-
_id: docs[1]._id,
|
|
130
|
-
_rev: current.body?._rev,
|
|
131
|
-
type: 'integration',
|
|
132
|
-
count: 3
|
|
133
|
-
}
|
|
134
|
-
])
|
|
135
|
-
|
|
136
|
-
assert.strictEqual(updateResults.length, 1)
|
|
137
|
-
const [updated] = updateResults
|
|
138
|
-
assert.ok(updated)
|
|
139
|
-
assert.strictEqual(updated.ok, true)
|
|
140
|
-
assert.ok(updated.rev)
|
|
141
|
-
|
|
142
|
-
const { body } = await getDoc(docs[1]._id)
|
|
143
|
-
assert.strictEqual(body?.count, 3)
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
await t.test('reports conflicts when revision is stale', async () => {
|
|
147
|
-
if (!docTwoInitialRev) throw new Error('Expected initial revision to be captured')
|
|
148
|
-
|
|
149
|
-
const conflictResults = await bulkSave(baseConfig, [
|
|
150
|
-
{
|
|
151
|
-
_id: docs[1]._id,
|
|
152
|
-
_rev: docTwoInitialRev,
|
|
153
|
-
type: 'integration',
|
|
154
|
-
count: 99
|
|
155
|
-
}
|
|
156
|
-
])
|
|
157
|
-
|
|
158
|
-
assert.strictEqual(conflictResults.length, 1)
|
|
159
|
-
const [conflict] = conflictResults
|
|
160
|
-
assert.ok(conflict)
|
|
161
|
-
assert.strictEqual(conflict.id, docs[1]._id)
|
|
162
|
-
assert.strictEqual(conflict.error, 'conflict')
|
|
163
|
-
assert.ok(conflict.reason)
|
|
164
|
-
})
|
|
165
|
-
})
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
suite('bulkSaveTransaction', () => {
|
|
169
|
-
test('integration with pouchdb-server', async t => {
|
|
170
|
-
await t.test('completes transaction for new and existing docs', async () => {
|
|
171
|
-
const emitter = createTestEmitter()
|
|
172
|
-
const config: CouchConfigInput = {
|
|
173
|
-
...transactionBaseConfig,
|
|
174
|
-
'~emitter': emitter
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const existingId = `txn-existing-success-${Date.now()}`
|
|
178
|
-
const newId = `txn-new-success-${Date.now()}`
|
|
179
|
-
const transactionId = `bulk-transaction-success-${Date.now()}`
|
|
180
|
-
|
|
181
|
-
const existing = await saveDoc(TEST_DB_URL, existingId, {
|
|
182
|
-
type: 'transaction',
|
|
183
|
-
count: 1
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
const docs = [
|
|
187
|
-
{
|
|
188
|
-
_id: existingId,
|
|
189
|
-
_rev: existing.rev,
|
|
190
|
-
type: 'transaction',
|
|
191
|
-
count: 2
|
|
192
|
-
},
|
|
193
|
-
{ _id: newId, type: 'transaction', count: 1 }
|
|
194
|
-
]
|
|
195
|
-
|
|
196
|
-
const results = await bulkSaveTransaction(config, transactionId, docs)
|
|
197
|
-
assert.strictEqual(results.length, 2)
|
|
198
|
-
assert.ok(results[0]?.ok)
|
|
199
|
-
assert.strictEqual(results[0]?.id, existingId)
|
|
200
|
-
assert.ok(results[1]?.ok)
|
|
201
|
-
assert.strictEqual(results[1]?.id, newId)
|
|
202
|
-
|
|
203
|
-
const updatedExisting = await getDocFrom(TEST_DB_URL, existingId)
|
|
204
|
-
assert.strictEqual(updatedExisting.statusCode, 200)
|
|
205
|
-
assert.strictEqual(updatedExisting.body?.count, 2)
|
|
206
|
-
|
|
207
|
-
const createdDoc = await getDocFrom(TEST_DB_URL, newId)
|
|
208
|
-
assert.strictEqual(createdDoc.statusCode, 200)
|
|
209
|
-
assert.strictEqual(createdDoc.body?.count, 1)
|
|
210
|
-
|
|
211
|
-
const transactionDoc = await getDocFrom(TEST_DB_URL, `txn:${transactionId}`)
|
|
212
|
-
assert.strictEqual(transactionDoc.statusCode, 200)
|
|
213
|
-
assert.strictEqual(transactionDoc.body?.status, 'completed')
|
|
214
|
-
|
|
215
|
-
assert.ok(emitter.events.some(({ event }) => event === 'transaction-created'))
|
|
216
|
-
assert.ok(emitter.events.some(({ event }) => event === 'transaction-completed'))
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
await t.test('throws TransactionVersionConflictError when revisions mismatch', async () => {
|
|
220
|
-
const emitter = createTestEmitter()
|
|
221
|
-
const config: CouchConfigInput = {
|
|
222
|
-
...transactionBaseConfig,
|
|
223
|
-
'~emitter': emitter
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const docId = `txn-conflict-doc-${Date.now()}`
|
|
227
|
-
const transactionId = `bulk-transaction-conflict-${Date.now()}`
|
|
228
|
-
|
|
229
|
-
const first = await saveDoc(TEST_DB_URL, docId, {
|
|
230
|
-
type: 'conflict',
|
|
231
|
-
count: 1
|
|
232
|
-
})
|
|
233
|
-
await saveDoc(TEST_DB_URL, docId, {
|
|
234
|
-
_rev: first.rev,
|
|
235
|
-
type: 'conflict',
|
|
236
|
-
count: 2
|
|
237
|
-
})
|
|
238
|
-
await assert.rejects(
|
|
239
|
-
() =>
|
|
240
|
-
bulkSaveTransaction(config, transactionId, [
|
|
241
|
-
{
|
|
242
|
-
_id: docId,
|
|
243
|
-
_rev: first.rev,
|
|
244
|
-
type: 'conflict',
|
|
245
|
-
count: 3
|
|
246
|
-
}
|
|
247
|
-
]),
|
|
248
|
-
(err: unknown) =>
|
|
249
|
-
err instanceof TransactionVersionConflictError && err.conflictingIds.includes(docId)
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
assert.ok(emitter.events.some(({ event }) => event === 'transaction-created'))
|
|
253
|
-
assert.ok(emitter.events.some(({ event }) => event === 'transaction-revs-fetched'))
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
await t.test('rolls back changes when bulk save fails', async () => {
|
|
257
|
-
const emitter = createTestEmitter()
|
|
258
|
-
const config: CouchConfigInput = {
|
|
259
|
-
...transactionBaseConfig,
|
|
260
|
-
'~emitter': emitter
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const successId = `txn-rollback-existing-${Date.now()}`
|
|
264
|
-
const conflictId = `txn-rollback-conflict-${Date.now()}`
|
|
265
|
-
const transactionId = `bulk-transaction-rollback-${Date.now()}`
|
|
266
|
-
|
|
267
|
-
const existing = await saveDoc(TEST_DB_URL, successId, {
|
|
268
|
-
type: 'rollback',
|
|
269
|
-
count: 1
|
|
270
|
-
})
|
|
271
|
-
const conflicting = await saveDoc(TEST_DB_URL, conflictId, {
|
|
272
|
-
type: 'rollback',
|
|
273
|
-
count: 1
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
emitter.on('transaction-revs-checked', async () => {
|
|
277
|
-
await needle(
|
|
278
|
-
'put',
|
|
279
|
-
`${TEST_DB_URL}/${conflictId}`,
|
|
280
|
-
{
|
|
281
|
-
_id: conflictId,
|
|
282
|
-
_rev: conflicting.rev,
|
|
283
|
-
type: 'rollback',
|
|
284
|
-
count: 99
|
|
285
|
-
},
|
|
286
|
-
{ json: true }
|
|
287
|
-
)
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
await assert.rejects(
|
|
291
|
-
() =>
|
|
292
|
-
bulkSaveTransaction(config, transactionId, [
|
|
293
|
-
{ _id: successId, _rev: existing.rev, type: 'rollback', count: 2 },
|
|
294
|
-
{
|
|
295
|
-
_id: conflictId,
|
|
296
|
-
_rev: conflicting.rev,
|
|
297
|
-
type: 'rollback',
|
|
298
|
-
count: 2
|
|
299
|
-
}
|
|
300
|
-
]),
|
|
301
|
-
(err: unknown) => err instanceof TransactionRollbackError
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
const rolledBack = await getDocFrom(TEST_DB_URL, successId)
|
|
305
|
-
assert.strictEqual(rolledBack.statusCode, 200)
|
|
306
|
-
assert.strictEqual(rolledBack.body?.count, 1)
|
|
307
|
-
|
|
308
|
-
const conflicted = await getDocFrom(TEST_DB_URL, conflictId)
|
|
309
|
-
assert.strictEqual(conflicted.statusCode, 200)
|
|
310
|
-
assert.strictEqual(conflicted.body?.count, 99)
|
|
311
|
-
|
|
312
|
-
const transactionDoc = await getDocFrom(TEST_DB_URL, `txn:${transactionId}`)
|
|
313
|
-
assert.strictEqual(transactionDoc.statusCode, 200)
|
|
314
|
-
assert.strictEqual(transactionDoc.body?.status, 'rolled_back')
|
|
315
|
-
|
|
316
|
-
assert.ok(emitter.events.some(({ event }) => event === 'transaction-rolled-back'))
|
|
317
|
-
})
|
|
318
|
-
})
|
|
319
|
-
})
|
package/impl/get.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 { z } from 'zod'
|
|
5
|
-
import type { CouchConfigInput } from '../schema/config.mts'
|
|
6
|
-
import { get, getAtRev } from './get.mts'
|
|
7
|
-
import { NotFoundError, RetryableError } from './utils/errors.mts'
|
|
8
|
-
import { TEST_DB_URL } from '../test/setup-db.mts'
|
|
9
|
-
|
|
10
|
-
const baseConfig: CouchConfigInput = {
|
|
11
|
-
couch: TEST_DB_URL
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
type DocBody = Record<string, unknown>
|
|
15
|
-
|
|
16
|
-
async function saveDoc(id: string, body: DocBody) {
|
|
17
|
-
const response = await needle(
|
|
18
|
-
'put',
|
|
19
|
-
`${TEST_DB_URL}/${id}`,
|
|
20
|
-
{
|
|
21
|
-
_id: id,
|
|
22
|
-
...body
|
|
23
|
-
},
|
|
24
|
-
{ json: true }
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
if (response.statusCode !== 201 && response.statusCode !== 200) {
|
|
28
|
-
throw new Error(`Failed to save document ${id}: ${response.statusCode}`)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return response.body as { rev: string }
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
suite('get', () => {
|
|
35
|
-
test('integration with pouchdb-server', async t => {
|
|
36
|
-
const doc_valid_id = `doc-valid-${Date.now()}`
|
|
37
|
-
const doc_invalid_id = `doc-invalid-${Date.now()}`
|
|
38
|
-
const doc_rev_id = `doc-rev-${Date.now()}`
|
|
39
|
-
await saveDoc(doc_valid_id, { kind: 'example', count: 7 })
|
|
40
|
-
await saveDoc(doc_invalid_id, { kind: 'example', count: 'oops' })
|
|
41
|
-
const firstRev = await saveDoc(doc_rev_id, { version: 1 })
|
|
42
|
-
await saveDoc(doc_rev_id, { _rev: firstRev.rev, version: 2 })
|
|
43
|
-
|
|
44
|
-
await t.test('returns documents and validates schema', async () => {
|
|
45
|
-
const schema = z.looseObject({
|
|
46
|
-
_id: z.string(),
|
|
47
|
-
kind: z.literal('example'),
|
|
48
|
-
count: z.number()
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
const doc = await get(baseConfig, doc_valid_id, {
|
|
52
|
-
validate: { docSchema: schema }
|
|
53
|
-
})
|
|
54
|
-
assert.ok(doc)
|
|
55
|
-
assert.strictEqual(doc?.kind, 'example')
|
|
56
|
-
assert.strictEqual(doc?.count, 7)
|
|
57
|
-
|
|
58
|
-
await assert.rejects(
|
|
59
|
-
() => get(baseConfig, doc_invalid_id, { validate: { docSchema: schema } }),
|
|
60
|
-
(err: unknown) => {
|
|
61
|
-
return (
|
|
62
|
-
Array.isArray(err) &&
|
|
63
|
-
err[0].message === 'Invalid input: expected number, received string'
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
)
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
await t.test('returns null when not found by default', async () => {
|
|
70
|
-
const missing = await get(baseConfig, 'doc-missing')
|
|
71
|
-
assert.strictEqual(missing, null)
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
await t.test('throws NotFoundError when configured', async () => {
|
|
75
|
-
const strictConfig: CouchConfigInput = {
|
|
76
|
-
...baseConfig,
|
|
77
|
-
throwOnGetNotFound: true
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
await assert.rejects(
|
|
81
|
-
() => get(strictConfig, 'doc-missing'),
|
|
82
|
-
(err: unknown) => err instanceof NotFoundError && err.docId === 'doc-missing'
|
|
83
|
-
)
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
await t.test('getAtRev returns specific revision', async () => {
|
|
87
|
-
const versionedSchema = z.looseObject({
|
|
88
|
-
_id: z.string(),
|
|
89
|
-
version: z.number()
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
const latest = await get(baseConfig, doc_rev_id, {
|
|
93
|
-
validate: { docSchema: versionedSchema }
|
|
94
|
-
})
|
|
95
|
-
assert.strictEqual(latest?.version, 2)
|
|
96
|
-
|
|
97
|
-
const early = await getAtRev(baseConfig, doc_rev_id, firstRev.rev, {
|
|
98
|
-
validate: { docSchema: versionedSchema }
|
|
99
|
-
})
|
|
100
|
-
assert.strictEqual(early?.version, 1)
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
await t.test('propagates retryable network errors', async () => {
|
|
104
|
-
const offlineConfig: CouchConfigInput = {
|
|
105
|
-
couch: 'http://localhost:6553/offline-db'
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
await assert.rejects(
|
|
109
|
-
() => get(offlineConfig, 'doc-valid'),
|
|
110
|
-
(err: unknown) => err instanceof RetryableError && err.statusCode === 503
|
|
111
|
-
)
|
|
112
|
-
})
|
|
113
|
-
})
|
|
114
|
-
})
|
package/impl/getDBInfo.test.mts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert/strict'
|
|
2
|
-
import { createServer } from 'node:http'
|
|
3
|
-
import test, { suite } from 'node:test'
|
|
4
|
-
import type { CouchConfigInput } from '../schema/config.mts'
|
|
5
|
-
import { getDBInfo } from './getDBInfo.mts'
|
|
6
|
-
import { RetryableError } from './utils/errors.mts'
|
|
7
|
-
import { TEST_DB_URL } from '../test/setup-db.mts'
|
|
8
|
-
|
|
9
|
-
suite('getDBInfo', () => {
|
|
10
|
-
test('it should throw if provided config is invalid', async () => {
|
|
11
|
-
await assert.rejects(async () => {
|
|
12
|
-
await getDBInfo({
|
|
13
|
-
// @ts-expect-error testing invalid config
|
|
14
|
-
notAnOption: true,
|
|
15
|
-
// @ts-expect-error testing invalid config
|
|
16
|
-
couch: DB_URL,
|
|
17
|
-
useConsoleLogger: true
|
|
18
|
-
})
|
|
19
|
-
})
|
|
20
|
-
})
|
|
21
|
-
test('integration with pouchdb-server', async t => {
|
|
22
|
-
await t.test('returns database metadata', async () => {
|
|
23
|
-
const config: CouchConfigInput = { couch: TEST_DB_URL }
|
|
24
|
-
const info = await getDBInfo(config)
|
|
25
|
-
assert.strictEqual(info.db_name, 'hide-a-bed-test-db')
|
|
26
|
-
assert.ok(typeof info.doc_count === 'number')
|
|
27
|
-
})
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
test('throws RetryableError when server marks response retryable', async t => {
|
|
31
|
-
const port = 8993
|
|
32
|
-
const server = createServer((_req, res) => {
|
|
33
|
-
res.statusCode = 503
|
|
34
|
-
res.setHeader('Content-Type', 'application/json')
|
|
35
|
-
res.end(JSON.stringify({ reason: 'maintenance' }))
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
await new Promise<void>(resolve => {
|
|
39
|
-
server.listen(port, resolve)
|
|
40
|
-
})
|
|
41
|
-
t.after(() => {
|
|
42
|
-
server.close()
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
await assert.rejects(
|
|
46
|
-
() => getDBInfo({ couch: `http://localhost:${port}/retryable` }),
|
|
47
|
-
(err: unknown) => {
|
|
48
|
-
assert.ok(err instanceof RetryableError)
|
|
49
|
-
assert.strictEqual(err.statusCode, 503)
|
|
50
|
-
assert.strictEqual(err.message, 'maintenance')
|
|
51
|
-
return true
|
|
52
|
-
}
|
|
53
|
-
)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
test('converts network failures into RetryableError', async () => {
|
|
57
|
-
await assert.rejects(
|
|
58
|
-
() => getDBInfo({ couch: 'http://localhost:6555/offline-db' }),
|
|
59
|
-
(err: unknown) => err instanceof RetryableError && err.statusCode === 503
|
|
60
|
-
)
|
|
61
|
-
})
|
|
62
|
-
})
|
package/impl/patch.test.mts
DELETED
|
@@ -1,142 +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 { get } from './get.mts'
|
|
6
|
-
import { patch, patchDangerously } from './patch.mts'
|
|
7
|
-
import { TEST_DB_URL } from '../test/setup-db.mts'
|
|
8
|
-
|
|
9
|
-
const baseConfig: CouchConfigInput = {
|
|
10
|
-
couch: TEST_DB_URL,
|
|
11
|
-
initialDelay: 10,
|
|
12
|
-
maxRetries: 2,
|
|
13
|
-
backoffFactor: 1.2
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type DocBody = Record<string, unknown>
|
|
17
|
-
|
|
18
|
-
async function saveDoc(id: string, body: DocBody) {
|
|
19
|
-
const response = await needle(
|
|
20
|
-
'put',
|
|
21
|
-
`${TEST_DB_URL}/${id}`,
|
|
22
|
-
{
|
|
23
|
-
_id: id,
|
|
24
|
-
...body
|
|
25
|
-
},
|
|
26
|
-
{ json: true }
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
if (response.statusCode !== 201 && response.statusCode !== 200) {
|
|
30
|
-
throw new Error(`Failed to save document ${id}: ${response.statusCode}`)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return response.body as { rev: string }
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
suite('patch', () => {
|
|
37
|
-
test('it will throw if provided config is invalid', async () => {
|
|
38
|
-
await assert.rejects(async () => {
|
|
39
|
-
// @ts-expect-error testing invalid config
|
|
40
|
-
await patch({ notAnOption: true, couch: DB_URL, useConsoleLogger: true }, 'doc1', {
|
|
41
|
-
foo: 'bar'
|
|
42
|
-
})
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
await assert.rejects(async () => {
|
|
46
|
-
await patchDangerously(
|
|
47
|
-
// @ts-expect-error testing invalid config
|
|
48
|
-
{ anotherBadOption: 123, couch: DB_URL, useConsoleLogger: true },
|
|
49
|
-
'doc1',
|
|
50
|
-
{ foo: 'bar' }
|
|
51
|
-
)
|
|
52
|
-
})
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
test('patch should throw if document revision is not provided', async () => {
|
|
56
|
-
await assert.rejects(
|
|
57
|
-
async () => {
|
|
58
|
-
// @ts-expect-error testing missing _rev
|
|
59
|
-
await patch(baseConfig, 'doc-no-rev', { foo: 'bar' })
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
message: /_rev is required for patch operations/
|
|
63
|
-
}
|
|
64
|
-
)
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
test('integration with pouchdb-server', async t => {
|
|
68
|
-
const patch_doc_id = `patch-doc-${Date.now()}`
|
|
69
|
-
const initial = await saveDoc(patch_doc_id, { message: 'original' })
|
|
70
|
-
|
|
71
|
-
await t.test('patch updates document when revision matches', async () => {
|
|
72
|
-
const result = await patch(baseConfig, patch_doc_id, {
|
|
73
|
-
_rev: initial.rev,
|
|
74
|
-
message: 'patched',
|
|
75
|
-
updated: true
|
|
76
|
-
})
|
|
77
|
-
assert.ok(result.ok)
|
|
78
|
-
assert.ok(result.rev)
|
|
79
|
-
|
|
80
|
-
const doc = await get(baseConfig, patch_doc_id)
|
|
81
|
-
assert.strictEqual(doc?.message, 'patched')
|
|
82
|
-
assert.strictEqual(doc?.updated, true)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
await t.test('patch returns conflict on stale revision', async () => {
|
|
86
|
-
const current = await get(baseConfig, patch_doc_id)
|
|
87
|
-
const staleRev = initial.rev
|
|
88
|
-
|
|
89
|
-
const conflict = await patch(baseConfig, patch_doc_id, {
|
|
90
|
-
_rev: staleRev,
|
|
91
|
-
message: 'should-fail'
|
|
92
|
-
})
|
|
93
|
-
assert.strictEqual(conflict.ok, false)
|
|
94
|
-
assert.strictEqual(conflict.statusCode, 409)
|
|
95
|
-
assert.strictEqual(conflict.error, 'conflict')
|
|
96
|
-
|
|
97
|
-
const doc = await get(baseConfig, patch_doc_id)
|
|
98
|
-
assert.strictEqual(doc?.message, current?.message)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
await t.test('patchDangerously merges properties without revision', async () => {
|
|
102
|
-
const result = await patchDangerously(baseConfig, patch_doc_id, {
|
|
103
|
-
description: 'dangerously updated'
|
|
104
|
-
})
|
|
105
|
-
assert.ok(result?.ok)
|
|
106
|
-
|
|
107
|
-
const doc = await get(baseConfig, patch_doc_id)
|
|
108
|
-
assert.strictEqual(doc?.description, 'dangerously updated')
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
await t.test('patchDangerously returns not_found when document missing', async () => {
|
|
112
|
-
const response = await patchDangerously(baseConfig, 'missing-doc', {
|
|
113
|
-
message: 'noop'
|
|
114
|
-
})
|
|
115
|
-
assert.strictEqual(response?.ok, false)
|
|
116
|
-
assert.strictEqual(response?.statusCode, 404)
|
|
117
|
-
assert.strictEqual(response?.error, 'not_found')
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
await t.test('patchDangerously reports failure after exhausting retries', async () => {
|
|
121
|
-
const doc = await get(baseConfig, patch_doc_id)
|
|
122
|
-
const conflictConfig: CouchConfigInput = {
|
|
123
|
-
...baseConfig,
|
|
124
|
-
maxRetries: 1,
|
|
125
|
-
initialDelay: 1,
|
|
126
|
-
backoffFactor: 1
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const response = await patchDangerously(conflictConfig, patch_doc_id, {
|
|
130
|
-
_rev: initial.rev,
|
|
131
|
-
conflicted: true
|
|
132
|
-
})
|
|
133
|
-
assert.strictEqual(response?.ok, false)
|
|
134
|
-
assert.strictEqual(response?.statusCode, 500)
|
|
135
|
-
assert.match(response?.error ?? '', /Failed to patch after 1 attempts/)
|
|
136
|
-
|
|
137
|
-
const current = await get(baseConfig, patch_doc_id)
|
|
138
|
-
assert.strictEqual(current?.conflicted, undefined)
|
|
139
|
-
assert.strictEqual(current?._rev, doc?._rev)
|
|
140
|
-
})
|
|
141
|
-
})
|
|
142
|
-
})
|