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.
Files changed (107) hide show
  1. package/migration_guides/v6.md +70 -0
  2. package/package.json +4 -1
  3. package/docs/.nojekyll +0 -1
  4. package/docs/assets/hierarchy.js +0 -1
  5. package/docs/assets/highlight.css +0 -113
  6. package/docs/assets/icons.js +0 -18
  7. package/docs/assets/icons.svg +0 -1
  8. package/docs/assets/main.js +0 -60
  9. package/docs/assets/navigation.js +0 -1
  10. package/docs/assets/search.js +0 -1
  11. package/docs/assets/style.css +0 -1633
  12. package/docs/classes/QueryBuilder.html +0 -42
  13. package/docs/functions/bindConfig.html +0 -4
  14. package/docs/functions/bulkGet.html +0 -14
  15. package/docs/functions/bulkGetDictionary.html +0 -10
  16. package/docs/functions/bulkRemove.html +0 -12
  17. package/docs/functions/bulkRemoveMap.html +0 -11
  18. package/docs/functions/bulkSave.html +0 -10
  19. package/docs/functions/bulkSaveTransaction.html +0 -23
  20. package/docs/functions/createLock.html +0 -7
  21. package/docs/functions/createQuery.html +0 -1
  22. package/docs/functions/get.html +0 -1
  23. package/docs/functions/getAtRev.html +0 -1
  24. package/docs/functions/getDBInfo.html +0 -10
  25. package/docs/functions/patch.html +0 -8
  26. package/docs/functions/patchDangerously.html +0 -9
  27. package/docs/functions/put.html +0 -1
  28. package/docs/functions/query.html +0 -15
  29. package/docs/functions/queryStream.html +0 -6
  30. package/docs/functions/remove.html +0 -1
  31. package/docs/functions/removeLock.html +0 -6
  32. package/docs/functions/watchDocs.html +0 -9
  33. package/docs/functions/withRetry.html +0 -6
  34. package/docs/hierarchy.html +0 -1
  35. package/docs/index.html +0 -483
  36. package/docs/interfaces/NetworkError.html +0 -6
  37. package/docs/interfaces/NotFoundError.html +0 -10
  38. package/docs/interfaces/RetryOptions.html +0 -10
  39. package/docs/interfaces/RetryableError.html +0 -10
  40. package/docs/interfaces/StandardSchemaV1.FailureResult.html +0 -4
  41. package/docs/interfaces/StandardSchemaV1.Issue.html +0 -6
  42. package/docs/interfaces/StandardSchemaV1.Options.html +0 -3
  43. package/docs/interfaces/StandardSchemaV1.PathSegment.html +0 -4
  44. package/docs/interfaces/StandardSchemaV1.Props.html +0 -10
  45. package/docs/interfaces/StandardSchemaV1.SuccessResult.html +0 -6
  46. package/docs/interfaces/StandardSchemaV1.Types.html +0 -6
  47. package/docs/interfaces/StandardSchemaV1.html +0 -4
  48. package/docs/modules/StandardSchemaV1.html +0 -1
  49. package/docs/modules.html +0 -1
  50. package/docs/types/BoundInstance.html +0 -1
  51. package/docs/types/BulkGetBound.html +0 -2
  52. package/docs/types/BulkGetDictionaryBound.html +0 -1
  53. package/docs/types/BulkGetDictionaryOptions.html +0 -2
  54. package/docs/types/BulkGetDictionaryResult.html +0 -3
  55. package/docs/types/BulkGetOptions.html +0 -3
  56. package/docs/types/BulkGetResponse.html +0 -1
  57. package/docs/types/CouchConfig-1.html +0 -1
  58. package/docs/types/CouchConfig.html +0 -1
  59. package/docs/types/CouchConfigInput.html +0 -1
  60. package/docs/types/CouchDoc-1.html +0 -1
  61. package/docs/types/CouchDoc.html +0 -2
  62. package/docs/types/CouchDocInput.html +0 -2
  63. package/docs/types/GetAtRevBound.html +0 -1
  64. package/docs/types/GetBound.html +0 -1
  65. package/docs/types/GetOptions.html +0 -2
  66. package/docs/types/LockDoc-1.html +0 -1
  67. package/docs/types/LockDoc.html +0 -1
  68. package/docs/types/LockOptions-1.html +0 -1
  69. package/docs/types/LockOptions.html +0 -1
  70. package/docs/types/LockOptionsInput.html +0 -1
  71. package/docs/types/OnInvalidDocAction.html +0 -1
  72. package/docs/types/OnRow.html +0 -1
  73. package/docs/types/QueryBound.html +0 -1
  74. package/docs/types/SimpleViewOptions-1.html +0 -1
  75. package/docs/types/SimpleViewOptions.html +0 -1
  76. package/docs/types/StandardSchemaV1.InferInput.html +0 -2
  77. package/docs/types/StandardSchemaV1.InferOutput.html +0 -2
  78. package/docs/types/StandardSchemaV1.Result.html +0 -2
  79. package/docs/types/ViewQueryResponse-1.html +0 -1
  80. package/docs/types/ViewQueryResponse.html +0 -2
  81. package/docs/types/ViewQueryResponseValidated.html +0 -2
  82. package/docs/types/ViewRow-1.html +0 -1
  83. package/docs/types/ViewRow.html +0 -2
  84. package/docs/types/ViewRowValidated.html +0 -7
  85. package/docs/types/ViewString.html +0 -1
  86. package/docs/types/WatchOptionsInput.html +0 -1
  87. package/docs/types/WatchOptionsSchema-1.html +0 -1
  88. package/docs/types/WatchOptionsSchema.html +0 -1
  89. package/impl/bulkGet.test.mts +0 -159
  90. package/impl/bulkRemove.test.mts +0 -102
  91. package/impl/bulkSave.test.mts +0 -319
  92. package/impl/get.test.mts +0 -114
  93. package/impl/getDBInfo.test.mts +0 -62
  94. package/impl/patch.test.mts +0 -142
  95. package/impl/put.test.mts +0 -114
  96. package/impl/query.test.mts +0 -280
  97. package/impl/remove.test.mts +0 -82
  98. package/impl/retry.test.mts +0 -77
  99. package/impl/stream.test.mts +0 -205
  100. package/impl/sugar/lock.test.mts +0 -113
  101. package/impl/sugar/watch.test.mts +0 -155
  102. package/impl/utils/errors.test.mts +0 -58
  103. package/impl/utils/logger.test.mts +0 -129
  104. package/impl/utils/parseRows.test.mts +0 -183
  105. package/impl/utils/queryBuilder.test.mts +0 -83
  106. package/impl/utils/queryString.test.mts +0 -53
  107. 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
- })
@@ -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
- })
@@ -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
- })
@@ -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
- })