hide-a-bed 5.2.7 → 6.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.
Files changed (363) hide show
  1. package/.prettierrc +7 -0
  2. package/README.md +270 -218
  3. package/dist/cjs/index.cjs +1952 -0
  4. package/dist/esm/index.mjs +1898 -0
  5. package/docs/.nojekyll +1 -0
  6. package/docs/assets/hierarchy.js +1 -0
  7. package/docs/assets/highlight.css +113 -0
  8. package/docs/assets/icons.js +18 -0
  9. package/docs/assets/icons.svg +1 -0
  10. package/docs/assets/main.js +60 -0
  11. package/docs/assets/navigation.js +1 -0
  12. package/docs/assets/search.js +1 -0
  13. package/docs/assets/style.css +1633 -0
  14. package/docs/classes/QueryBuilder.html +42 -0
  15. package/docs/functions/bindConfig.html +4 -0
  16. package/docs/functions/bulkGet.html +14 -0
  17. package/docs/functions/bulkGetDictionary.html +10 -0
  18. package/docs/functions/bulkRemove.html +12 -0
  19. package/docs/functions/bulkRemoveMap.html +11 -0
  20. package/docs/functions/bulkSave.html +10 -0
  21. package/docs/functions/bulkSaveTransaction.html +23 -0
  22. package/docs/functions/createLock.html +7 -0
  23. package/docs/functions/createQuery.html +1 -0
  24. package/docs/functions/get.html +1 -0
  25. package/docs/functions/getAtRev.html +1 -0
  26. package/docs/functions/getDBInfo.html +10 -0
  27. package/docs/functions/patch.html +8 -0
  28. package/docs/functions/patchDangerously.html +9 -0
  29. package/docs/functions/put.html +1 -0
  30. package/docs/functions/query.html +15 -0
  31. package/docs/functions/queryStream.html +6 -0
  32. package/docs/functions/remove.html +1 -0
  33. package/docs/functions/removeLock.html +6 -0
  34. package/docs/functions/watchDocs.html +9 -0
  35. package/docs/functions/withRetry.html +6 -0
  36. package/docs/hierarchy.html +1 -0
  37. package/docs/index.html +483 -0
  38. package/docs/interfaces/NetworkError.html +6 -0
  39. package/docs/interfaces/NotFoundError.html +10 -0
  40. package/docs/interfaces/RetryOptions.html +10 -0
  41. package/docs/interfaces/RetryableError.html +10 -0
  42. package/docs/interfaces/StandardSchemaV1.FailureResult.html +4 -0
  43. package/docs/interfaces/StandardSchemaV1.Issue.html +6 -0
  44. package/docs/interfaces/StandardSchemaV1.Options.html +3 -0
  45. package/docs/interfaces/StandardSchemaV1.PathSegment.html +4 -0
  46. package/docs/interfaces/StandardSchemaV1.Props.html +10 -0
  47. package/docs/interfaces/StandardSchemaV1.SuccessResult.html +6 -0
  48. package/docs/interfaces/StandardSchemaV1.Types.html +6 -0
  49. package/docs/interfaces/StandardSchemaV1.html +4 -0
  50. package/docs/modules/StandardSchemaV1.html +1 -0
  51. package/docs/modules.html +1 -0
  52. package/docs/types/BoundInstance.html +1 -0
  53. package/docs/types/BulkGetBound.html +2 -0
  54. package/docs/types/BulkGetDictionaryBound.html +1 -0
  55. package/docs/types/BulkGetDictionaryOptions.html +2 -0
  56. package/docs/types/BulkGetDictionaryResult.html +3 -0
  57. package/docs/types/BulkGetOptions.html +3 -0
  58. package/docs/types/BulkGetResponse.html +1 -0
  59. package/docs/types/CouchConfig-1.html +1 -0
  60. package/docs/types/CouchConfig.html +1 -0
  61. package/docs/types/CouchConfigInput.html +1 -0
  62. package/docs/types/CouchDoc-1.html +1 -0
  63. package/docs/types/CouchDoc.html +2 -0
  64. package/docs/types/CouchDocInput.html +2 -0
  65. package/docs/types/GetAtRevBound.html +1 -0
  66. package/docs/types/GetBound.html +1 -0
  67. package/docs/types/GetOptions.html +2 -0
  68. package/docs/types/LockDoc-1.html +1 -0
  69. package/docs/types/LockDoc.html +1 -0
  70. package/docs/types/LockOptions-1.html +1 -0
  71. package/docs/types/LockOptions.html +1 -0
  72. package/docs/types/LockOptionsInput.html +1 -0
  73. package/docs/types/OnInvalidDocAction.html +1 -0
  74. package/docs/types/OnRow.html +1 -0
  75. package/docs/types/QueryBound.html +1 -0
  76. package/docs/types/SimpleViewOptions-1.html +1 -0
  77. package/docs/types/SimpleViewOptions.html +1 -0
  78. package/docs/types/StandardSchemaV1.InferInput.html +2 -0
  79. package/docs/types/StandardSchemaV1.InferOutput.html +2 -0
  80. package/docs/types/StandardSchemaV1.Result.html +2 -0
  81. package/docs/types/ViewQueryResponse-1.html +1 -0
  82. package/docs/types/ViewQueryResponse.html +2 -0
  83. package/docs/types/ViewQueryResponseValidated.html +2 -0
  84. package/docs/types/ViewRow-1.html +1 -0
  85. package/docs/types/ViewRow.html +2 -0
  86. package/docs/types/ViewRowValidated.html +7 -0
  87. package/docs/types/ViewString.html +1 -0
  88. package/docs/types/WatchOptionsInput.html +1 -0
  89. package/docs/types/WatchOptionsSchema-1.html +1 -0
  90. package/docs/types/WatchOptionsSchema.html +1 -0
  91. package/eslint.config.js +15 -0
  92. package/impl/bindConfig.mts +140 -0
  93. package/impl/bulkGet.mts +256 -0
  94. package/impl/bulkGet.test.mts +159 -0
  95. package/impl/bulkRemove.mts +98 -0
  96. package/impl/bulkRemove.test.mts +102 -0
  97. package/impl/bulkSave.mts +286 -0
  98. package/impl/bulkSave.test.mts +319 -0
  99. package/impl/get.mts +137 -0
  100. package/impl/get.test.mts +114 -0
  101. package/impl/getDBInfo.mts +67 -0
  102. package/impl/getDBInfo.test.mts +62 -0
  103. package/impl/patch.mts +134 -0
  104. package/impl/patch.test.mts +142 -0
  105. package/impl/put.mts +56 -0
  106. package/impl/put.test.mts +114 -0
  107. package/impl/query.mts +224 -0
  108. package/impl/query.test.mts +280 -0
  109. package/impl/remove.mts +65 -0
  110. package/impl/remove.test.mts +82 -0
  111. package/impl/retry.mts +66 -0
  112. package/impl/retry.test.mts +77 -0
  113. package/impl/stream.mts +143 -0
  114. package/impl/stream.test.mts +205 -0
  115. package/impl/sugar/lock.mts +103 -0
  116. package/impl/sugar/lock.test.mts +113 -0
  117. package/impl/sugar/{watch.mjs → watch.mts} +56 -22
  118. package/impl/sugar/watch.test.mts +155 -0
  119. package/impl/utils/errors.mts +130 -0
  120. package/impl/utils/errors.test.mts +58 -0
  121. package/impl/utils/logger.mts +62 -0
  122. package/impl/utils/logger.test.mts +129 -0
  123. package/impl/utils/mergeNeedleOpts.mts +16 -0
  124. package/impl/utils/parseRows.mts +117 -0
  125. package/impl/utils/parseRows.test.mts +183 -0
  126. package/impl/utils/queryBuilder.mts +173 -0
  127. package/impl/utils/queryBuilder.test.mts +83 -0
  128. package/impl/utils/queryString.mts +44 -0
  129. package/impl/utils/queryString.test.mts +53 -0
  130. package/impl/{trackedEmitter.mjs → utils/trackedEmitter.mts} +9 -7
  131. package/impl/utils/transactionErrors.mts +71 -0
  132. package/index.mts +82 -0
  133. package/index.test.mts +415 -0
  134. package/package.json +45 -31
  135. package/schema/config.mts +81 -0
  136. package/schema/couch/couch.input.schema.ts +43 -0
  137. package/schema/couch/couch.output.schema.ts +169 -0
  138. package/schema/sugar/lock.mts +18 -0
  139. package/schema/sugar/watch.mts +14 -0
  140. package/schema/util.mts +8 -0
  141. package/tsconfig.json +10 -4
  142. package/tsdown.config.ts +16 -0
  143. package/typedoc.json +4 -0
  144. package/types/output/eslint.config.d.ts +3 -0
  145. package/types/output/eslint.config.d.ts.map +1 -0
  146. package/types/output/impl/bindConfig.d.mts +174 -0
  147. package/types/output/impl/bindConfig.d.mts.map +1 -0
  148. package/types/output/impl/bulkGet.d.mts +75 -0
  149. package/types/output/impl/bulkGet.d.mts.map +1 -0
  150. package/types/output/impl/bulkGet.test.d.mts +2 -0
  151. package/types/output/impl/bulkGet.test.d.mts.map +1 -0
  152. package/types/output/impl/bulkRemove.d.mts +63 -0
  153. package/types/output/impl/bulkRemove.d.mts.map +1 -0
  154. package/types/output/impl/bulkRemove.test.d.mts +2 -0
  155. package/types/output/impl/bulkRemove.test.d.mts.map +1 -0
  156. package/types/output/impl/bulkSave.d.mts +64 -0
  157. package/types/output/impl/bulkSave.d.mts.map +1 -0
  158. package/types/output/impl/bulkSave.test.d.mts +2 -0
  159. package/types/output/impl/bulkSave.test.d.mts.map +1 -0
  160. package/types/output/impl/get.d.mts +20 -0
  161. package/types/output/impl/get.d.mts.map +1 -0
  162. package/types/output/impl/get.test.d.mts +2 -0
  163. package/types/output/impl/get.test.d.mts.map +1 -0
  164. package/types/output/impl/getDBInfo.d.mts +52 -0
  165. package/types/output/impl/getDBInfo.d.mts.map +1 -0
  166. package/types/output/impl/getDBInfo.test.d.mts +2 -0
  167. package/types/output/impl/getDBInfo.test.d.mts.map +1 -0
  168. package/types/output/impl/patch.d.mts +45 -0
  169. package/types/output/impl/patch.d.mts.map +1 -0
  170. package/types/output/impl/patch.test.d.mts +2 -0
  171. package/types/output/impl/patch.test.d.mts.map +1 -0
  172. package/types/output/impl/put.d.mts +5 -0
  173. package/types/output/impl/put.d.mts.map +1 -0
  174. package/types/output/impl/put.test.d.mts +2 -0
  175. package/types/output/impl/put.test.d.mts.map +1 -0
  176. package/types/output/impl/query.d.mts +47 -0
  177. package/types/output/impl/query.d.mts.map +1 -0
  178. package/types/output/impl/query.test.d.mts +2 -0
  179. package/types/output/impl/query.test.d.mts.map +1 -0
  180. package/types/output/impl/remove.d.mts +9 -0
  181. package/types/output/impl/remove.d.mts.map +1 -0
  182. package/types/output/impl/remove.test.d.mts +2 -0
  183. package/types/output/impl/remove.test.d.mts.map +1 -0
  184. package/types/output/impl/retry.d.mts +32 -0
  185. package/types/output/impl/retry.d.mts.map +1 -0
  186. package/types/output/impl/retry.test.d.mts +2 -0
  187. package/types/output/impl/retry.test.d.mts.map +1 -0
  188. package/types/output/impl/stream.d.mts +13 -0
  189. package/types/output/impl/stream.d.mts.map +1 -0
  190. package/types/output/impl/stream.test.d.mts +2 -0
  191. package/types/output/impl/stream.test.d.mts.map +1 -0
  192. package/types/output/impl/sugar/lock.d.mts +24 -0
  193. package/types/output/impl/sugar/lock.d.mts.map +1 -0
  194. package/types/output/impl/sugar/lock.test.d.mts +2 -0
  195. package/types/output/impl/sugar/lock.test.d.mts.map +1 -0
  196. package/types/output/impl/sugar/watch.d.mts +21 -0
  197. package/types/output/impl/sugar/watch.d.mts.map +1 -0
  198. package/types/output/impl/sugar/watch.test.d.mts +2 -0
  199. package/types/output/impl/sugar/watch.test.d.mts.map +1 -0
  200. package/types/output/impl/utils/errors.d.mts +78 -0
  201. package/types/output/impl/utils/errors.d.mts.map +1 -0
  202. package/types/output/impl/utils/errors.test.d.mts +2 -0
  203. package/types/output/impl/utils/errors.test.d.mts.map +1 -0
  204. package/types/output/impl/utils/logger.d.mts +11 -0
  205. package/types/output/impl/utils/logger.d.mts.map +1 -0
  206. package/types/output/impl/utils/logger.test.d.mts +2 -0
  207. package/types/output/impl/utils/logger.test.d.mts.map +1 -0
  208. package/types/output/impl/utils/mergeNeedleOpts.d.mts +53 -0
  209. package/types/output/impl/utils/mergeNeedleOpts.d.mts.map +1 -0
  210. package/types/output/impl/utils/parseRows.d.mts +15 -0
  211. package/types/output/impl/utils/parseRows.d.mts.map +1 -0
  212. package/types/output/impl/utils/parseRows.test.d.mts +2 -0
  213. package/types/output/impl/utils/parseRows.test.d.mts.map +1 -0
  214. package/types/output/impl/utils/queryBuilder.d.mts +68 -0
  215. package/types/output/impl/utils/queryBuilder.d.mts.map +1 -0
  216. package/types/output/impl/utils/queryBuilder.test.d.mts +2 -0
  217. package/types/output/impl/utils/queryBuilder.test.d.mts.map +1 -0
  218. package/types/output/impl/utils/queryString.d.mts +9 -0
  219. package/types/output/impl/utils/queryString.d.mts.map +1 -0
  220. package/types/output/impl/utils/queryString.test.d.mts +2 -0
  221. package/types/output/impl/utils/queryString.test.d.mts.map +1 -0
  222. package/types/output/impl/utils/trackedEmitter.d.mts +7 -0
  223. package/types/output/impl/utils/trackedEmitter.d.mts.map +1 -0
  224. package/{impl → types/output/impl/utils}/transactionErrors.d.mts +16 -31
  225. package/types/output/impl/utils/transactionErrors.d.mts.map +1 -0
  226. package/types/output/index.d.mts +32 -0
  227. package/types/output/index.d.mts.map +1 -0
  228. package/types/output/index.test.d.mts +2 -0
  229. package/types/output/index.test.d.mts.map +1 -0
  230. package/types/output/schema/config.d.mts +90 -0
  231. package/types/output/schema/config.d.mts.map +1 -0
  232. package/types/output/schema/couch/couch.input.schema.d.ts +29 -0
  233. package/types/output/schema/couch/couch.input.schema.d.ts.map +1 -0
  234. package/types/output/schema/couch/couch.output.schema.d.ts +113 -0
  235. package/types/output/schema/couch/couch.output.schema.d.ts.map +1 -0
  236. package/types/output/schema/sugar/lock.d.mts +19 -0
  237. package/types/output/schema/sugar/lock.d.mts.map +1 -0
  238. package/types/output/schema/sugar/watch.d.mts +11 -0
  239. package/types/output/schema/sugar/watch.d.mts.map +1 -0
  240. package/types/output/schema/util.d.mts +85 -0
  241. package/types/output/schema/util.d.mts.map +1 -0
  242. package/types/output/tsdown.config.d.ts +3 -0
  243. package/types/output/tsdown.config.d.ts.map +1 -0
  244. package/types/output/types/standard-schema.d.ts +60 -0
  245. package/types/output/types/standard-schema.d.ts.map +1 -0
  246. package/types/standard-schema.ts +76 -0
  247. package/types/utils.d.ts +1 -0
  248. package/cjs/impl/bulk.cjs +0 -275
  249. package/cjs/impl/changes.cjs +0 -67
  250. package/cjs/impl/crud.cjs +0 -127
  251. package/cjs/impl/errors.cjs +0 -75
  252. package/cjs/impl/logger.cjs +0 -70
  253. package/cjs/impl/patch.cjs +0 -95
  254. package/cjs/impl/query.cjs +0 -116
  255. package/cjs/impl/queryBuilder.cjs +0 -163
  256. package/cjs/impl/retry.cjs +0 -55
  257. package/cjs/impl/stream.cjs +0 -121
  258. package/cjs/impl/sugar/lock.cjs +0 -81
  259. package/cjs/impl/sugar/watch.cjs +0 -159
  260. package/cjs/impl/trackedEmitter.cjs +0 -54
  261. package/cjs/impl/transactionErrors.cjs +0 -70
  262. package/cjs/impl/util.cjs +0 -64
  263. package/cjs/index.cjs +0 -132
  264. package/cjs/integration/changes.cjs +0 -76
  265. package/cjs/integration/disconnect-watch.cjs +0 -52
  266. package/cjs/integration/watch.cjs +0 -59
  267. package/cjs/schema/bind.cjs +0 -59
  268. package/cjs/schema/bulk.cjs +0 -92
  269. package/cjs/schema/changes.cjs +0 -68
  270. package/cjs/schema/config.cjs +0 -48
  271. package/cjs/schema/crud.cjs +0 -77
  272. package/cjs/schema/patch.cjs +0 -53
  273. package/cjs/schema/query.cjs +0 -62
  274. package/cjs/schema/stream.cjs +0 -42
  275. package/cjs/schema/sugar/lock.cjs +0 -59
  276. package/cjs/schema/sugar/watch.cjs +0 -42
  277. package/cjs/schema/util.cjs +0 -39
  278. package/config.json +0 -5
  279. package/docs/compiler.png +0 -0
  280. package/dualmode.config.json +0 -11
  281. package/impl/bulk.d.mts +0 -11
  282. package/impl/bulk.d.mts.map +0 -1
  283. package/impl/bulk.mjs +0 -291
  284. package/impl/changes.d.mts +0 -12
  285. package/impl/changes.d.mts.map +0 -1
  286. package/impl/changes.mjs +0 -53
  287. package/impl/crud.d.mts +0 -7
  288. package/impl/crud.d.mts.map +0 -1
  289. package/impl/crud.mjs +0 -108
  290. package/impl/errors.d.mts +0 -43
  291. package/impl/errors.d.mts.map +0 -1
  292. package/impl/errors.mjs +0 -65
  293. package/impl/logger.d.mts +0 -32
  294. package/impl/logger.d.mts.map +0 -1
  295. package/impl/logger.mjs +0 -59
  296. package/impl/patch.d.mts +0 -6
  297. package/impl/patch.d.mts.map +0 -1
  298. package/impl/patch.mjs +0 -88
  299. package/impl/query.d.mts +0 -195
  300. package/impl/query.d.mts.map +0 -1
  301. package/impl/query.mjs +0 -122
  302. package/impl/queryBuilder.d.mts +0 -154
  303. package/impl/queryBuilder.d.mts.map +0 -1
  304. package/impl/queryBuilder.mjs +0 -175
  305. package/impl/retry.d.mts +0 -2
  306. package/impl/retry.d.mts.map +0 -1
  307. package/impl/retry.mjs +0 -39
  308. package/impl/stream.d.mts +0 -3
  309. package/impl/stream.d.mts.map +0 -1
  310. package/impl/stream.mjs +0 -98
  311. package/impl/sugar/lock.d.mts +0 -5
  312. package/impl/sugar/lock.d.mts.map +0 -1
  313. package/impl/sugar/lock.mjs +0 -70
  314. package/impl/sugar/watch.d.mts +0 -34
  315. package/impl/sugar/watch.d.mts.map +0 -1
  316. package/impl/trackedEmitter.d.mts +0 -8
  317. package/impl/trackedEmitter.d.mts.map +0 -1
  318. package/impl/transactionErrors.d.mts.map +0 -1
  319. package/impl/transactionErrors.mjs +0 -47
  320. package/impl/util.d.mts +0 -3
  321. package/impl/util.d.mts.map +0 -1
  322. package/impl/util.mjs +0 -35
  323. package/index.d.mts +0 -80
  324. package/index.d.mts.map +0 -1
  325. package/index.mjs +0 -141
  326. package/integration/changes.mjs +0 -60
  327. package/integration/disconnect-watch.mjs +0 -36
  328. package/integration/watch.mjs +0 -40
  329. package/log.txt +0 -580
  330. package/schema/bind.d.mts +0 -5461
  331. package/schema/bind.d.mts.map +0 -1
  332. package/schema/bind.mjs +0 -43
  333. package/schema/bulk.d.mts +0 -923
  334. package/schema/bulk.d.mts.map +0 -1
  335. package/schema/bulk.mjs +0 -83
  336. package/schema/changes.d.mts +0 -191
  337. package/schema/changes.d.mts.map +0 -1
  338. package/schema/changes.mjs +0 -59
  339. package/schema/config.d.mts +0 -79
  340. package/schema/config.d.mts.map +0 -1
  341. package/schema/config.mjs +0 -26
  342. package/schema/crud.d.mts +0 -491
  343. package/schema/crud.d.mts.map +0 -1
  344. package/schema/crud.mjs +0 -64
  345. package/schema/patch.d.mts +0 -255
  346. package/schema/patch.d.mts.map +0 -1
  347. package/schema/patch.mjs +0 -42
  348. package/schema/query.d.mts +0 -406
  349. package/schema/query.d.mts.map +0 -1
  350. package/schema/query.mjs +0 -45
  351. package/schema/stream.d.mts +0 -211
  352. package/schema/stream.d.mts.map +0 -1
  353. package/schema/stream.mjs +0 -23
  354. package/schema/sugar/lock.d.mts +0 -238
  355. package/schema/sugar/lock.d.mts.map +0 -1
  356. package/schema/sugar/lock.mjs +0 -50
  357. package/schema/sugar/watch.d.mts +0 -127
  358. package/schema/sugar/watch.d.mts.map +0 -1
  359. package/schema/sugar/watch.mjs +0 -29
  360. package/schema/util.d.mts +0 -160
  361. package/schema/util.d.mts.map +0 -1
  362. package/schema/util.mjs +0 -35
  363. package/types/changes-stream.d.ts +0 -11
@@ -0,0 +1,143 @@
1
+ import needle from 'needle'
2
+ import type { IncomingMessage } from 'node:http'
3
+ import Chain from 'stream-chain'
4
+ import Parser from 'stream-json/Parser.js'
5
+ import Pick from 'stream-json/filters/Pick.js'
6
+ import StreamArray from 'stream-json/streamers/StreamArray.js'
7
+ import { CouchConfig, type CouchConfigInput } from '../schema/config.mts'
8
+ import { RetryableError } from './utils/errors.mts'
9
+ import { createLogger } from './utils/logger.mts'
10
+ import { queryString } from './utils/queryString.mts'
11
+ import { mergeNeedleOpts } from './utils/mergeNeedleOpts.mts'
12
+ import type { ViewRow } from '../schema/couch/couch.output.schema.ts'
13
+ import type { ViewOptions } from '../schema/couch/couch.input.schema.ts'
14
+
15
+ type StreamArrayChunk<Row> = {
16
+ key: number
17
+ value: Row
18
+ }
19
+
20
+ export type OnRow = (row: ViewRow) => void
21
+ type HttpMethod = 'GET' | 'POST'
22
+
23
+ /**
24
+ * Execute a CouchDB view query and stream rows as they are received.
25
+ * @param rawConfig CouchDB configuration
26
+ * @param view The CouchDB view to query
27
+ * @param options Query options
28
+ * @param onRow Callback invoked for each row received
29
+ */
30
+ export async function queryStream(
31
+ rawConfig: CouchConfigInput,
32
+ view: string,
33
+ options: ViewOptions | undefined,
34
+ onRow: OnRow
35
+ ): Promise<void> {
36
+ return new Promise((resolve, reject) => {
37
+ const config = CouchConfig.parse(rawConfig)
38
+ const logger = createLogger(config)
39
+ logger.info(`Starting view query stream: ${view}`)
40
+ logger.debug('Query options:', options)
41
+
42
+ const queryOptions: ViewOptions = options ?? {}
43
+
44
+ let method: HttpMethod = 'GET'
45
+ let payload: Record<string, unknown> | null = null
46
+ let qs = queryString(queryOptions)
47
+ logger.debug('Generated query string:', qs)
48
+
49
+ if (typeof queryOptions.keys !== 'undefined') {
50
+ const MAX_URL_LENGTH = 2000
51
+ const keysAsString = `keys=${encodeURIComponent(JSON.stringify(queryOptions.keys))}`
52
+
53
+ if (keysAsString.length + qs.length + 1 <= MAX_URL_LENGTH) {
54
+ qs += (qs.length > 0 ? '&' : '') + keysAsString
55
+ } else {
56
+ method = 'POST'
57
+ payload = { keys: queryOptions.keys }
58
+ }
59
+ }
60
+
61
+ const url = `${config.couch}/${view}?${qs}`
62
+ const opts = {
63
+ json: true,
64
+ headers: {
65
+ 'Content-Type': 'application/json'
66
+ },
67
+ parse_response: false as const
68
+ }
69
+ const mergedOpts = mergeNeedleOpts(config, opts)
70
+
71
+ const parserPipeline = Chain.chain([
72
+ new Parser(),
73
+ new Pick({ filter: 'rows' }),
74
+ new StreamArray()
75
+ ])
76
+
77
+ let rowCount = 0
78
+ let settled = false
79
+
80
+ const settleReject = (err: Error) => {
81
+ if (settled) return
82
+ settled = true
83
+ reject(err)
84
+ }
85
+
86
+ const settleResolve = () => {
87
+ if (settled) return
88
+ settled = true
89
+ resolve()
90
+ }
91
+
92
+ let request: ReturnType<typeof needle.get> | ReturnType<typeof needle.post> | null = null
93
+
94
+ parserPipeline.on('data', (chunk: StreamArrayChunk<ViewRow>) => {
95
+ try {
96
+ rowCount++
97
+ onRow(chunk.value)
98
+ } catch (callbackErr) {
99
+ const error = callbackErr instanceof Error ? callbackErr : new Error(String(callbackErr))
100
+ parserPipeline.destroy(error)
101
+ settleReject(error)
102
+ }
103
+ })
104
+
105
+ parserPipeline.on('error', (err: Error) => {
106
+ logger.error('Stream parsing error:', err)
107
+ parserPipeline.destroy()
108
+ settleReject(new Error(`Stream parsing error: ${err.message}`, { cause: err }))
109
+ })
110
+
111
+ parserPipeline.on('end', () => {
112
+ logger.info(`Stream completed, processed ${rowCount} rows`)
113
+ settleResolve()
114
+ })
115
+
116
+ request = method === 'GET' ? needle.get(url, mergedOpts) : needle.post(url, payload, mergedOpts)
117
+
118
+ request.on('response', (response: IncomingMessage) => {
119
+ logger.debug(`Received response with status code: ${response.statusCode}`)
120
+ if (RetryableError.isRetryableStatusCode(response.statusCode)) {
121
+ logger.warn(`Retryable status code received: ${response.statusCode}`)
122
+ settleReject(new RetryableError('retryable error during stream query', response.statusCode))
123
+ // @ts-expect-error bad type?
124
+ request.destroy()
125
+ }
126
+ })
127
+
128
+ request.on('error', (err: NodeJS.ErrnoException) => {
129
+ logger.error('Network error during stream query:', err)
130
+ parserPipeline.destroy(err)
131
+ try {
132
+ RetryableError.handleNetworkError(err)
133
+ } catch (retryErr) {
134
+ settleReject(retryErr as Error)
135
+ return
136
+ } finally {
137
+ settleReject(err)
138
+ }
139
+ })
140
+
141
+ request.pipe(parserPipeline)
142
+ })
143
+ }
@@ -0,0 +1,205 @@
1
+ import assert from 'node:assert/strict'
2
+ import test, { suite } from 'node:test'
3
+ import { createServer } from 'node:http'
4
+ import type { AddressInfo } from 'node:net'
5
+ import { queryStream } from './stream.mts'
6
+ import { bindConfig } from './bindConfig.mts'
7
+
8
+ const startServer = async (handler: Parameters<typeof createServer>[0]) => {
9
+ const server = createServer(handler)
10
+ await new Promise<void>(resolve => server.listen(0, resolve))
11
+ return server
12
+ }
13
+
14
+ suite('queryStream', () => {
15
+ test('queryStream streams rows from chunked response', async t => {
16
+ const expectedRows = [
17
+ { id: 'row-1', key: 'row-1', value: { count: 1 } },
18
+ { id: 'row-2', key: 'row-2', value: { count: 2 } }
19
+ ]
20
+
21
+ // @ts-expect-error testing server
22
+ const server = await startServer((req, res) => {
23
+ res.on('error', () => {})
24
+ const requestUrl = new URL(req.url ?? '/', `http://${req.headers.host}`)
25
+ assert.strictEqual(req.method, 'GET')
26
+ assert.strictEqual(requestUrl.pathname, '/_design/demo/_view/by-key')
27
+
28
+ res.writeHead(200, { 'Content-Type': 'application/json' })
29
+ const payload = JSON.stringify({
30
+ total_rows: expectedRows.length,
31
+ rows: expectedRows
32
+ })
33
+ const chunkSize = 7
34
+ for (let i = 0; i < payload.length; i += chunkSize) {
35
+ res.write(payload.slice(i, i + chunkSize))
36
+ }
37
+ res.end()
38
+ })
39
+
40
+ t.after(async () => {
41
+ await new Promise<void>(resolve => server.close(() => resolve()))
42
+ })
43
+
44
+ const { port } = server.address() as AddressInfo
45
+ const rows: unknown[] = []
46
+
47
+ await queryStream(
48
+ { couch: `http://127.0.0.1:${port}` },
49
+ '_design/demo/_view/by-key',
50
+ {},
51
+ row => {
52
+ const matchedRow = expectedRows.find(r => r.id === row.id)
53
+ assert.ok(matchedRow)
54
+ assert.deepStrictEqual(row, matchedRow)
55
+ rows.push(row)
56
+ }
57
+ )
58
+
59
+ assert.deepStrictEqual(rows, expectedRows)
60
+ })
61
+
62
+ test('queryStream works with bindConfig', async t => {
63
+ const expectedRows = [
64
+ { id: 'row-1', key: 'row-1', value: { count: 1 } },
65
+ { id: 'row-2', key: 'row-2', value: { count: 2 } }
66
+ ]
67
+
68
+ // @ts-expect-error testing server
69
+ const server = await startServer((req, res) => {
70
+ res.on('error', () => {})
71
+ const requestUrl = new URL(req.url ?? '/', `http://${req.headers.host}`)
72
+ assert.strictEqual(req.method, 'GET')
73
+ assert.strictEqual(requestUrl.pathname, '/_design/demo/_view/by-key')
74
+
75
+ res.writeHead(200, { 'Content-Type': 'application/json' })
76
+ const payload = JSON.stringify({
77
+ total_rows: expectedRows.length,
78
+ rows: expectedRows
79
+ })
80
+ const chunkSize = 7
81
+ for (let i = 0; i < payload.length; i += chunkSize) {
82
+ res.write(payload.slice(i, i + chunkSize))
83
+ }
84
+ res.end()
85
+ })
86
+
87
+ t.after(async () => {
88
+ await new Promise<void>(resolve => server.close(() => resolve()))
89
+ })
90
+
91
+ const { port } = server.address() as AddressInfo
92
+
93
+ const rows: unknown[] = []
94
+
95
+ const db = bindConfig({ couch: `http://127.0.0.1:${port}` })
96
+
97
+ await db.queryStream('_design/demo/_view/by-key', {}, row => {
98
+ const matchedRow = expectedRows.find(r => r.id === row.id)
99
+ assert.ok(matchedRow)
100
+ assert.deepStrictEqual(row, matchedRow)
101
+ rows.push(row)
102
+ })
103
+
104
+ assert.deepStrictEqual(rows, expectedRows)
105
+ })
106
+
107
+ test('queryStream works with options chaining', async t => {
108
+ const expectedRows = [
109
+ { id: 'row-1', key: 'row-1', value: { count: 1 } },
110
+ { id: 'row-2', key: 'row-2', value: { count: 2 } }
111
+ ]
112
+
113
+ // @ts-expect-error testing server
114
+ const server = await startServer((req, res) => {
115
+ res.on('error', () => {})
116
+ const requestUrl = new URL(req.url ?? '/', `http://${req.headers.host}`)
117
+ assert.strictEqual(req.method, 'GET')
118
+ assert.strictEqual(requestUrl.pathname, '/_design/demo/_view/by-key')
119
+
120
+ res.writeHead(200, { 'Content-Type': 'application/json' })
121
+ const payload = JSON.stringify({
122
+ total_rows: expectedRows.length,
123
+ rows: expectedRows
124
+ })
125
+ const chunkSize = 7
126
+ for (let i = 0; i < payload.length; i += chunkSize) {
127
+ res.write(payload.slice(i, i + chunkSize))
128
+ }
129
+ res.end()
130
+ })
131
+
132
+ t.after(async () => {
133
+ await new Promise<void>(resolve => server.close(() => resolve()))
134
+ })
135
+
136
+ const { port } = server.address() as AddressInfo
137
+ const rows: unknown[] = []
138
+
139
+ const db = bindConfig({ couch: `http://127.0.0.1:${port}` })
140
+
141
+ await db.options({ logger: console }).queryStream('_design/demo/_view/by-key', {}, row => {
142
+ const matchedRow = expectedRows.find(r => r.id === row.id)
143
+ assert.ok(matchedRow)
144
+ assert.deepStrictEqual(row, matchedRow)
145
+ rows.push(row)
146
+ })
147
+
148
+ assert.deepStrictEqual(rows, expectedRows)
149
+ })
150
+
151
+ test('queryStream handles empty result sets', async t => {
152
+ // @ts-expect-error testing server
153
+ const server = await startServer((_, res) => {
154
+ res.on('error', () => {})
155
+ res.writeHead(200, { 'Content-Type': 'application/json' })
156
+ res.write('{"rows":[]}')
157
+ res.end()
158
+ })
159
+
160
+ t.after(async () => {
161
+ await new Promise<void>(resolve => server.close(() => resolve()))
162
+ })
163
+
164
+ const { port } = server.address() as AddressInfo
165
+ let rowCount = 0
166
+
167
+ await queryStream(
168
+ { couch: `http://127.0.0.1:${port}` },
169
+ '_design/demo/_view/by-key',
170
+ {},
171
+ () => {
172
+ rowCount++
173
+ }
174
+ )
175
+
176
+ assert.strictEqual(rowCount, 0)
177
+ })
178
+
179
+ test('queryStream rejects when row handler throws', async t => {
180
+ // @ts-expect-error testing server
181
+ const server = await startServer((_, res) => {
182
+ res.on('error', () => {})
183
+ res.writeHead(200, { 'Content-Type': 'application/json' })
184
+ res.write('{"rows":[{"id":"broken","value":42}]}')
185
+ res.end()
186
+ })
187
+
188
+ t.after(async () => {
189
+ await new Promise<void>(resolve => server.close(() => resolve()))
190
+ })
191
+
192
+ const { port } = server.address() as AddressInfo
193
+ const handlerError = new Error('row-failure')
194
+
195
+ await assert.rejects(
196
+ queryStream({ couch: `http://127.0.0.1:${port}` }, '_design/demo/_view/error', {}, () => {
197
+ throw handlerError
198
+ }),
199
+ error => {
200
+ assert.strictEqual(error, handlerError)
201
+ return true
202
+ }
203
+ )
204
+ })
205
+ })
@@ -0,0 +1,103 @@
1
+ import { LockOptions } from '../../schema/sugar/lock.mts'
2
+ import { put } from '../put.mts'
3
+ import { get } from '../get.mts'
4
+ import { createLogger } from '../utils/logger.mts'
5
+ import { CouchConfig, type CouchConfigInput } from '../../schema/config.mts'
6
+ import { isConflictError } from '../utils/errors.mts'
7
+
8
+ /**
9
+ * Create a lock document for the specified document ID.
10
+ * Returns true if the lock was created, false if locking is disabled or a conflict occurred.
11
+ *
12
+ * @param configInput CouchDB configuration
13
+ * @param docId The document ID to lock
14
+ * @param lockOptions Locking options
15
+ *
16
+ * @return True if the lock was created, false otherwise
17
+ */
18
+ export async function createLock(
19
+ configInput: CouchConfigInput,
20
+ docId: string,
21
+ lockOptions: LockOptions
22
+ ): Promise<boolean> {
23
+ const config = CouchConfig.parse(configInput)
24
+ const options = LockOptions.parse(lockOptions)
25
+
26
+ const logger = createLogger(config)
27
+
28
+ if (!options.enableLocking) {
29
+ logger.debug('Locking disabled, returning true without creating lock')
30
+ return true
31
+ }
32
+
33
+ const _id = `lock-${docId}`
34
+ const lock = {
35
+ _id,
36
+ type: 'lock',
37
+ locks: docId,
38
+ lockedAt: new Date().toISOString(),
39
+ lockedBy: options.username
40
+ }
41
+
42
+ try {
43
+ const result = await put(config, lock)
44
+ logger.info(`Lock created for ${docId} by ${options.username}`)
45
+ return result.ok === true
46
+ } catch (error) {
47
+ if (isConflictError(error)) {
48
+ logger.warn(`Lock conflict for ${docId} - already locked`)
49
+ } else {
50
+ logger.error(`Error creating lock for ${docId}:`, error)
51
+ }
52
+ return false
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Remove the lock document for the specified document ID if owned by the caller.
58
+ *
59
+ * @param configInput CouchDB configuration
60
+ * @param docId The document ID to unlock
61
+ * @param lockOptions Locking options
62
+ *
63
+ * @return Promise that resolves when the unlock operation is complete
64
+ */
65
+ export async function removeLock(
66
+ configInput: CouchConfigInput,
67
+ docId: string,
68
+ lockOptions: LockOptions
69
+ ): Promise<void> {
70
+ const config = CouchConfig.parse(configInput)
71
+ const options = LockOptions.parse(lockOptions)
72
+ const logger = createLogger(config)
73
+
74
+ if (!options.enableLocking) {
75
+ logger.debug('Locking disabled, skipping unlock')
76
+ return
77
+ }
78
+
79
+ if (!docId) {
80
+ logger.warn('No docId provided for unlock')
81
+ return
82
+ }
83
+
84
+ const _id = `lock-${docId}`
85
+ const existingLock = await get(config, _id)
86
+
87
+ if (!existingLock) {
88
+ logger.debug(`No lock found for ${docId}`)
89
+ return
90
+ }
91
+
92
+ if (existingLock.lockedBy !== options.username) {
93
+ logger.warn(`Cannot remove lock for ${docId} - owned by ${existingLock.lockedBy}`)
94
+ return
95
+ }
96
+
97
+ try {
98
+ await put(config, { ...existingLock, _deleted: true })
99
+ logger.info(`Lock removed for ${docId}`)
100
+ } catch (error) {
101
+ logger.error(`Error removing lock for ${docId}:`, error)
102
+ }
103
+ }
@@ -0,0 +1,113 @@
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 { createLock, removeLock } from './lock.mts'
6
+ import { TEST_DB_URL } from '../../test/setup-db.mts'
7
+
8
+ const baseConfig: CouchConfigInput = {
9
+ couch: TEST_DB_URL,
10
+ useConsoleLogger: true
11
+ }
12
+
13
+ async function fetchLockDoc(docId: string) {
14
+ return await needle('get', `${TEST_DB_URL}/lock-${docId}`, null, {
15
+ json: true
16
+ })
17
+ }
18
+
19
+ suite('lock', () => {
20
+ test('integration with pouchdb-server', async t => {
21
+ await t.test('creates a lock document when enabled', async () => {
22
+ const docId = `lock-creates-${Date.now()}`
23
+ const created = await createLock(baseConfig, docId, {
24
+ enableLocking: true,
25
+ username: 'alice'
26
+ })
27
+ assert.strictEqual(created, true)
28
+
29
+ const response = await fetchLockDoc(docId)
30
+ assert.strictEqual(response.statusCode, 200)
31
+ const body = response.body as { lockedBy: string; locks: string }
32
+ assert.strictEqual(body.lockedBy, 'alice')
33
+ assert.strictEqual(body.locks, docId)
34
+ })
35
+
36
+ await t.test('returns true without writing when locking disabled', async () => {
37
+ const docId = `lock-disabled-${Date.now()}`
38
+ const created = await createLock(baseConfig, docId, {
39
+ enableLocking: false,
40
+ username: 'anyone'
41
+ })
42
+ assert.strictEqual(created, true)
43
+
44
+ const response = await fetchLockDoc(docId)
45
+ assert.strictEqual(response.statusCode, 404)
46
+ })
47
+
48
+ await t.test('returns false on conflict and keeps existing lock', async () => {
49
+ const docId = `lock-conflict-${Date.now()}`
50
+ await createLock(baseConfig, docId, {
51
+ enableLocking: true,
52
+ username: 'alice'
53
+ })
54
+ const created = await createLock(baseConfig, docId, {
55
+ enableLocking: true,
56
+ username: 'bob'
57
+ })
58
+ assert.strictEqual(created, false)
59
+
60
+ const response = await fetchLockDoc(docId)
61
+ assert.strictEqual(response.statusCode, 200)
62
+ const body = response.body as { lockedBy: string }
63
+ assert.strictEqual(body.lockedBy, 'alice')
64
+ })
65
+
66
+ await t.test('removes lock when owned by caller', async () => {
67
+ const docId = `lock-remove-${Date.now()}`
68
+ await createLock(baseConfig, docId, {
69
+ enableLocking: true,
70
+ username: 'alice'
71
+ })
72
+ await removeLock(baseConfig, docId, {
73
+ enableLocking: true,
74
+ username: 'alice'
75
+ })
76
+
77
+ const response = await fetchLockDoc(docId)
78
+ assert.strictEqual(response.statusCode, 404)
79
+ })
80
+
81
+ await t.test('skips removal when lock owned by someone else', async () => {
82
+ const docId = `lock-remove-others-${Date.now()}`
83
+ await createLock(baseConfig, docId, {
84
+ enableLocking: true,
85
+ username: 'alice'
86
+ })
87
+ await removeLock(baseConfig, docId, {
88
+ enableLocking: true,
89
+ username: 'bob'
90
+ })
91
+
92
+ const response = await fetchLockDoc(docId)
93
+ assert.strictEqual(response.statusCode, 200)
94
+ const body = response.body as { lockedBy: string }
95
+ assert.strictEqual(body.lockedBy, 'alice')
96
+ })
97
+
98
+ await t.test('respects disabled removal', async () => {
99
+ const docId = `lock-disabled-remove-${Date.now()}`
100
+ await createLock(baseConfig, docId, {
101
+ enableLocking: true,
102
+ username: 'alice'
103
+ })
104
+ await removeLock(baseConfig, docId, {
105
+ enableLocking: false,
106
+ username: 'alice'
107
+ })
108
+
109
+ const response = await fetchLockDoc(docId)
110
+ assert.strictEqual(response.statusCode, 200)
111
+ })
112
+ })
113
+ })
@@ -1,18 +1,43 @@
1
1
  import needle from 'needle'
2
2
  import { EventEmitter } from 'events'
3
- import { RetryableError } from '../errors.mjs'
4
- import { createLogger } from '../logger.mjs'
5
- import { sleep } from '../patch.mjs'
6
- import { WatchDocs } from '../../schema/sugar/watch.mjs'
7
-
8
- // watch the doc for any changes
9
- export const watchDocs = WatchDocs.implement((config, docIds, onChange, options = {}) => {
3
+ import { RetryableError } from '../utils/errors.mts'
4
+ import { createLogger } from '../utils/logger.mts'
5
+ import { WatchOptions, type WatchOptionsInput } from '../../schema/sugar/watch.mts'
6
+ import { mergeNeedleOpts } from '../utils/mergeNeedleOpts.mts'
7
+ import { setTimeout } from 'node:timers/promises'
8
+ import {
9
+ CouchConfig,
10
+ type CouchConfigInput,
11
+ type NeedleBaseOptionsSchema
12
+ } from '../../schema/config.mts'
13
+
14
+ /**
15
+ * Watch for changes to specified document IDs in CouchDB.
16
+ * Calls the onChange callback for each change detected.
17
+ * Returns an emitter with methods to listen for events and stop watching.
18
+ *
19
+ * @param configInput CouchDB configuration
20
+ * @param docIds Document ID or array of document IDs to watch
21
+ * @param onChange Callback function called on each change
22
+ * @param optionsInput Watch options
23
+ *
24
+ * @return WatchEmitter with methods to manage the watch
25
+ */
26
+ export function watchDocs(
27
+ configInput: CouchConfigInput,
28
+ docIds: string | string[],
29
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
+ onChange: (change: any) => void,
31
+ optionsInput: WatchOptionsInput = {}
32
+ ) {
33
+ const config = CouchConfig.parse(configInput)
34
+ const options = WatchOptions.parse(optionsInput)
10
35
  const logger = createLogger(config)
11
36
  const emitter = new EventEmitter()
12
- let lastSeq = null || 'now'
37
+ let lastSeq: null | 'now' = null
13
38
  let stopping = false
14
39
  let retryCount = 0
15
- let currentRequest = null
40
+ let currentRequest: null | ReturnType<typeof needle.get> = null
16
41
  const maxRetries = options.maxRetries || 10
17
42
  const initialDelay = options.initialDelay || 1000
18
43
  const maxDelay = options.maxDelay || 30000
@@ -29,13 +54,17 @@ export const watchDocs = WatchDocs.implement((config, docIds, onChange, options
29
54
  const ids = _docIds.join('","')
30
55
  const url = `${config.couch}/_changes?feed=${feed}&since=${lastSeq}&include_docs=${includeDocs}&filter=_doc_ids&doc_ids=["${ids}"]`
31
56
 
32
- const opts = {
33
- headers: { 'Content-Type': 'application/json' },
57
+ const opts: NeedleBaseOptionsSchema = {
58
+ json: false,
59
+ headers: {
60
+ 'Content-Type': 'application/json'
61
+ },
34
62
  parse_response: false
35
63
  }
64
+ const mergedOpts = mergeNeedleOpts(config, opts)
36
65
 
37
66
  let buffer = ''
38
- currentRequest = needle.get(url, opts)
67
+ currentRequest = needle.get(url, mergedOpts)
39
68
 
40
69
  currentRequest.on('data', chunk => {
41
70
  buffer += chunk.toString()
@@ -61,13 +90,16 @@ export const watchDocs = WatchDocs.implement((config, docIds, onChange, options
61
90
  })
62
91
 
63
92
  currentRequest.on('response', response => {
64
- logger.debug(`Received response with status code, watching [${_docIds}]: ${response.statusCode}`)
93
+ logger.debug(
94
+ `Received response with status code, watching [${_docIds}]: ${response.statusCode}`
95
+ )
65
96
  if (RetryableError.isRetryableStatusCode(response.statusCode)) {
66
97
  logger.warn(`Retryable status code received: ${response.statusCode}`)
67
- currentRequest.abort()
98
+ // @ts-expect-error bad type?
99
+ currentRequest?.destroy()
68
100
  handleReconnect()
69
101
  } else {
70
- // Reset retry count on successful connection
102
+ // Reset retry count on successful connection
71
103
  retryCount = 0
72
104
  }
73
105
  })
@@ -85,14 +117,14 @@ export const watchDocs = WatchDocs.implement((config, docIds, onChange, options
85
117
  logger.info(`Retryable error, watching [${_docIds}]:`, filteredError.toString())
86
118
  handleReconnect()
87
119
  } else {
88
- logger.error(`Non-retryable error, watching [${_docIds}]`, filteredError.toString())
120
+ logger.error(`Non-retryable error, watching [${_docIds}]`, filteredError?.toString())
89
121
  emitter.emit('error', filteredError)
90
122
  }
91
123
  }
92
124
  })
93
125
 
94
126
  currentRequest.on('end', () => {
95
- // Process any remaining data in buffer
127
+ // Process any remaining data in buffer
96
128
  if (buffer.trim()) {
97
129
  try {
98
130
  const change = JSON.parse(buffer)
@@ -125,7 +157,7 @@ export const watchDocs = WatchDocs.implement((config, docIds, onChange, options
125
157
  retryCount++
126
158
 
127
159
  logger.info(`Attempting to reconnect in ${delay}ms (attempt ${retryCount} of ${maxRetries})`)
128
- await sleep(delay)
160
+ await setTimeout(delay)
129
161
 
130
162
  try {
131
163
  connect()
@@ -142,13 +174,15 @@ export const watchDocs = WatchDocs.implement((config, docIds, onChange, options
142
174
  emitter.on('change', onChange)
143
175
 
144
176
  return {
145
- on: (event, listener) => emitter.on(event, listener),
146
- removeListener: (event, listener) => emitter.removeListener(event, listener),
177
+ on: (event: string, listener: EventListener) => emitter.on(event, listener),
178
+ removeListener: (event: string, listener: EventListener) =>
179
+ emitter.removeListener(event, listener),
147
180
  stop: () => {
148
181
  stopping = true
149
- if (currentRequest) currentRequest.abort()
182
+ // @ts-expect-error bad type?
183
+ if (currentRequest) currentRequest.destroy()
150
184
  emitter.emit('end', { lastSeq })
151
185
  emitter.removeAllListeners()
152
186
  }
153
187
  }
154
- })
188
+ }