@harperfast/harper 5.3.0-alpha.1 → 5.3.0-beta.2

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 (347) hide show
  1. package/bin/cliOperations.ts +12 -0
  2. package/bin/restart.ts +66 -6
  3. package/components/Application.ts +1134 -112
  4. package/components/DESIGN.md +421 -0
  5. package/components/OptionsWatcher.ts +368 -102
  6. package/components/Scope.ts +20 -1
  7. package/components/componentLoader.ts +43 -4
  8. package/components/deploymentOperations.ts +4 -1
  9. package/components/deploymentRecorder.ts +9 -2
  10. package/components/mcp/DESIGN.md +109 -0
  11. package/components/mcp/audit.ts +21 -17
  12. package/components/operations.js +284 -52
  13. package/components/operationsValidation.js +49 -2
  14. package/components/packageComponent.ts +25 -1
  15. package/components/requestRestart.ts +11 -0
  16. package/config/DESIGN.md +306 -0
  17. package/config/RootConfigWatcher.ts +191 -37
  18. package/config/configReadRetry.ts +62 -0
  19. package/config/configUtils.ts +78 -26
  20. package/config/parseConfigFile.ts +34 -0
  21. package/config/readConfigFileSync.ts +44 -0
  22. package/config/watcherArming.ts +59 -0
  23. package/config-root.schema.json +4 -0
  24. package/dataLayer/DESIGN.md +179 -0
  25. package/dataLayer/harperBridge/ResourceBridge.ts +28 -2
  26. package/dataLayer/restoreMarker.ts +92 -25
  27. package/dist/bin/cliOperations.js +13 -0
  28. package/dist/bin/cliOperations.js.map +1 -1
  29. package/dist/bin/restart.js +42 -6
  30. package/dist/bin/restart.js.map +1 -1
  31. package/dist/components/Application.d.ts +104 -9
  32. package/dist/components/Application.js +954 -102
  33. package/dist/components/Application.js.map +1 -1
  34. package/dist/components/OptionsWatcher.d.ts +4 -1
  35. package/dist/components/OptionsWatcher.js +378 -104
  36. package/dist/components/OptionsWatcher.js.map +1 -1
  37. package/dist/components/Scope.js +15 -1
  38. package/dist/components/Scope.js.map +1 -1
  39. package/dist/components/componentLoader.js +35 -3
  40. package/dist/components/componentLoader.js.map +1 -1
  41. package/dist/components/deploymentOperations.js +4 -1
  42. package/dist/components/deploymentOperations.js.map +1 -1
  43. package/dist/components/deploymentRecorder.d.ts +4 -2
  44. package/dist/components/deploymentRecorder.js +1 -0
  45. package/dist/components/deploymentRecorder.js.map +1 -1
  46. package/dist/components/mcp/audit.d.ts +2 -1
  47. package/dist/components/mcp/audit.js +21 -17
  48. package/dist/components/mcp/audit.js.map +1 -1
  49. package/dist/components/operations.d.ts +28 -0
  50. package/dist/components/operations.js +263 -54
  51. package/dist/components/operations.js.map +1 -1
  52. package/dist/components/operationsValidation.js +48 -2
  53. package/dist/components/operationsValidation.js.map +1 -1
  54. package/dist/components/packageComponent.js +24 -0
  55. package/dist/components/packageComponent.js.map +1 -1
  56. package/dist/components/requestRestart.d.ts +1 -0
  57. package/dist/components/requestRestart.js +7 -0
  58. package/dist/components/requestRestart.js.map +1 -1
  59. package/dist/config/RootConfigWatcher.d.ts +2 -0
  60. package/dist/config/RootConfigWatcher.js +189 -35
  61. package/dist/config/RootConfigWatcher.js.map +1 -1
  62. package/dist/config/configReadRetry.d.ts +8 -0
  63. package/dist/config/configReadRetry.js +62 -0
  64. package/dist/config/configReadRetry.js.map +1 -0
  65. package/dist/config/configUtils.d.ts +10 -9
  66. package/dist/config/configUtils.js +63 -27
  67. package/dist/config/configUtils.js.map +1 -1
  68. package/dist/config/parseConfigFile.d.ts +4 -0
  69. package/dist/config/parseConfigFile.js +35 -0
  70. package/dist/config/parseConfigFile.js.map +1 -0
  71. package/dist/config/readConfigFileSync.d.ts +1 -0
  72. package/dist/config/readConfigFileSync.js +47 -0
  73. package/dist/config/readConfigFileSync.js.map +1 -0
  74. package/dist/config/watcherArming.d.ts +15 -0
  75. package/dist/config/watcherArming.js +59 -0
  76. package/dist/config/watcherArming.js.map +1 -0
  77. package/dist/dataLayer/harperBridge/ResourceBridge.js +21 -2
  78. package/dist/dataLayer/harperBridge/ResourceBridge.js.map +1 -1
  79. package/dist/dataLayer/restoreMarker.d.ts +21 -8
  80. package/dist/dataLayer/restoreMarker.js +94 -27
  81. package/dist/dataLayer/restoreMarker.js.map +1 -1
  82. package/dist/index.d.ts +2 -0
  83. package/dist/index.js +9 -1
  84. package/dist/index.js.map +1 -1
  85. package/dist/json/systemSchema.json +3 -0
  86. package/dist/resources/DatabaseTransaction.d.ts +25 -0
  87. package/dist/resources/DatabaseTransaction.js +224 -18
  88. package/dist/resources/DatabaseTransaction.js.map +1 -1
  89. package/dist/resources/LMDBTransaction.d.ts +2 -1
  90. package/dist/resources/LMDBTransaction.js +22 -3
  91. package/dist/resources/LMDBTransaction.js.map +1 -1
  92. package/dist/resources/PrimaryRocksDatabase.js +22 -6
  93. package/dist/resources/PrimaryRocksDatabase.js.map +1 -1
  94. package/dist/resources/RecordEncoder.d.ts +1 -1
  95. package/dist/resources/RecordEncoder.js +46 -10
  96. package/dist/resources/RecordEncoder.js.map +1 -1
  97. package/dist/resources/Resource.js +97 -13
  98. package/dist/resources/Resource.js.map +1 -1
  99. package/dist/resources/ResourceInterface.d.ts +8 -0
  100. package/dist/resources/RocksIndexStore.js +2 -1
  101. package/dist/resources/RocksIndexStore.js.map +1 -1
  102. package/dist/resources/RocksTransactionLogStore.d.ts +10 -0
  103. package/dist/resources/RocksTransactionLogStore.js +104 -33
  104. package/dist/resources/RocksTransactionLogStore.js.map +1 -1
  105. package/dist/resources/Table.d.ts +58 -7
  106. package/dist/resources/Table.js +1143 -348
  107. package/dist/resources/Table.js.map +1 -1
  108. package/dist/resources/analytics/write.d.ts +3 -0
  109. package/dist/resources/analytics/write.js +49 -16
  110. package/dist/resources/analytics/write.js.map +1 -1
  111. package/dist/resources/auditStore.d.ts +170 -0
  112. package/dist/resources/auditStore.js +457 -11
  113. package/dist/resources/auditStore.js.map +1 -1
  114. package/dist/resources/crdt.d.ts +10 -0
  115. package/dist/resources/crdt.js +22 -0
  116. package/dist/resources/crdt.js.map +1 -1
  117. package/dist/resources/dataLoader.js +3 -4
  118. package/dist/resources/dataLoader.js.map +1 -1
  119. package/dist/resources/databases.d.ts +16 -13
  120. package/dist/resources/databases.js +625 -177
  121. package/dist/resources/databases.js.map +1 -1
  122. package/dist/resources/derivedIndexRegistry.d.ts +5 -0
  123. package/dist/resources/derivedIndexRegistry.js +68 -0
  124. package/dist/resources/derivedIndexRegistry.js.map +1 -0
  125. package/dist/resources/derivedIndexRuntime.d.ts +215 -0
  126. package/dist/resources/derivedIndexRuntime.js +2027 -0
  127. package/dist/resources/derivedIndexRuntime.js.map +1 -0
  128. package/dist/resources/graphql.js +3 -2
  129. package/dist/resources/graphql.js.map +1 -1
  130. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.d.ts +102 -9
  131. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js +867 -39
  132. package/dist/resources/indexes/HierarchicalNavigableSmallWorld.js.map +1 -1
  133. package/dist/resources/indexes/fullTextDerivedIndex.d.ts +81 -0
  134. package/dist/resources/indexes/fullTextDerivedIndex.js +1004 -0
  135. package/dist/resources/indexes/fullTextDerivedIndex.js.map +1 -0
  136. package/dist/resources/indexes/fullTextNativeBinding.d.ts +78 -0
  137. package/dist/resources/indexes/fullTextNativeBinding.js +85 -0
  138. package/dist/resources/indexes/fullTextNativeBinding.js.map +1 -0
  139. package/dist/resources/indexes/hnswDerivedIndex.d.ts +67 -0
  140. package/dist/resources/indexes/hnswDerivedIndex.js +464 -0
  141. package/dist/resources/indexes/hnswDerivedIndex.js.map +1 -0
  142. package/dist/resources/indexes/hnswPlaneBinding.d.ts +65 -0
  143. package/dist/resources/indexes/hnswPlaneBinding.js +91 -0
  144. package/dist/resources/indexes/hnswPlaneBinding.js.map +1 -0
  145. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.d.ts +24 -0
  146. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js +149 -0
  147. package/dist/resources/indexes/nativeFullTextDerivedIndexLifecycle.js.map +1 -0
  148. package/dist/resources/nodeIdMapping.d.ts +5 -0
  149. package/dist/resources/nodeIdMapping.js +49 -0
  150. package/dist/resources/nodeIdMapping.js.map +1 -1
  151. package/dist/resources/recordLock.d.ts +47 -4
  152. package/dist/resources/recordLock.js +138 -7
  153. package/dist/resources/recordLock.js.map +1 -1
  154. package/dist/resources/recordLockCoordinator.d.ts +557 -0
  155. package/dist/resources/recordLockCoordinator.js +2597 -0
  156. package/dist/resources/recordLockCoordinator.js.map +1 -0
  157. package/dist/resources/replayLogs.js +5 -0
  158. package/dist/resources/replayLogs.js.map +1 -1
  159. package/dist/resources/replicatedApplyFailure.d.ts +16 -0
  160. package/dist/resources/replicatedApplyFailure.js +63 -0
  161. package/dist/resources/replicatedApplyFailure.js.map +1 -0
  162. package/dist/resources/scheduler/scheduler.js +3 -3
  163. package/dist/resources/scheduler/scheduler.js.map +1 -1
  164. package/dist/resources/search.d.ts +10 -4
  165. package/dist/resources/search.js +160 -40
  166. package/dist/resources/search.js.map +1 -1
  167. package/dist/resources/tracked.d.ts +5 -1
  168. package/dist/resources/tracked.js +74 -23
  169. package/dist/resources/tracked.js.map +1 -1
  170. package/dist/resources/transactionBroadcast.js +4 -6
  171. package/dist/resources/transactionBroadcast.js.map +1 -1
  172. package/dist/security/auth.js +59 -23
  173. package/dist/security/auth.js.map +1 -1
  174. package/dist/security/deferredAuthentication.d.ts +11 -0
  175. package/dist/security/deferredAuthentication.js +25 -3
  176. package/dist/security/deferredAuthentication.js.map +1 -1
  177. package/dist/security/jsLoader.js +9 -4
  178. package/dist/security/jsLoader.js.map +1 -1
  179. package/dist/server/REST.js +39 -5
  180. package/dist/server/REST.js.map +1 -1
  181. package/dist/server/http.d.ts +5 -1
  182. package/dist/server/http.js +34 -2
  183. package/dist/server/http.js.map +1 -1
  184. package/dist/server/mqtt.js +5 -1
  185. package/dist/server/mqtt.js.map +1 -1
  186. package/dist/server/serverHelpers/Headers.d.ts +2 -0
  187. package/dist/server/serverHelpers/Headers.js +6 -0
  188. package/dist/server/serverHelpers/Headers.js.map +1 -1
  189. package/dist/server/serverHelpers/NodeAdapterResponse.d.ts +48 -0
  190. package/dist/server/serverHelpers/NodeAdapterResponse.js +220 -0
  191. package/dist/server/serverHelpers/NodeAdapterResponse.js.map +1 -0
  192. package/dist/server/serverHelpers/Request.d.ts +5 -10
  193. package/dist/server/serverHelpers/Request.js +38 -136
  194. package/dist/server/serverHelpers/Request.js.map +1 -1
  195. package/dist/server/serverHelpers/contentTypes.d.ts +2 -0
  196. package/dist/server/serverHelpers/contentTypes.js +189 -15
  197. package/dist/server/serverHelpers/contentTypes.js.map +1 -1
  198. package/dist/server/serverHelpers/serverUtilities.d.ts +3 -3
  199. package/dist/server/serverHelpers/serverUtilities.js +110 -20
  200. package/dist/server/serverHelpers/serverUtilities.js.map +1 -1
  201. package/dist/server/serverHelpers/uwsServer.js +4 -1
  202. package/dist/server/serverHelpers/uwsServer.js.map +1 -1
  203. package/dist/server/serverHelpers/webSocketCloseReason.d.ts +2 -0
  204. package/dist/server/serverHelpers/webSocketCloseReason.js +29 -0
  205. package/dist/server/serverHelpers/webSocketCloseReason.js.map +1 -0
  206. package/dist/server/storageReclamation.js +1 -1
  207. package/dist/server/storageReclamation.js.map +1 -1
  208. package/dist/server/threads/isolatedApplications.d.ts +47 -0
  209. package/dist/server/threads/isolatedApplications.js +171 -0
  210. package/dist/server/threads/isolatedApplications.js.map +1 -0
  211. package/dist/server/threads/logRotationTransport.d.ts +1 -0
  212. package/dist/server/threads/logRotationTransport.js +33 -0
  213. package/dist/server/threads/logRotationTransport.js.map +1 -0
  214. package/dist/server/threads/manageThreads.d.ts +64 -6
  215. package/dist/server/threads/manageThreads.js +261 -12
  216. package/dist/server/threads/manageThreads.js.map +1 -1
  217. package/dist/server/threads/socketRouter.d.ts +1 -0
  218. package/dist/server/threads/socketRouter.js +196 -13
  219. package/dist/server/threads/socketRouter.js.map +1 -1
  220. package/dist/server/threads/threadServer.js +30 -7
  221. package/dist/server/threads/threadServer.js.map +1 -1
  222. package/dist/utility/errors/hdbError.d.ts +24 -0
  223. package/dist/utility/errors/hdbError.js +58 -1
  224. package/dist/utility/errors/hdbError.js.map +1 -1
  225. package/dist/utility/hdbTerms.d.ts +2 -0
  226. package/dist/utility/hdbTerms.js +2 -0
  227. package/dist/utility/hdbTerms.js.map +1 -1
  228. package/dist/utility/logging/harper_logger.js +218 -38
  229. package/dist/utility/logging/harper_logger.js.map +1 -1
  230. package/dist/utility/logging/logGenerationCoordinator.d.ts +35 -0
  231. package/dist/utility/logging/logGenerationCoordinator.js +184 -0
  232. package/dist/utility/logging/logGenerationCoordinator.js.map +1 -0
  233. package/dist/utility/logging/logRotation.d.ts +46 -0
  234. package/dist/utility/logging/logRotation.js +365 -0
  235. package/dist/utility/logging/logRotation.js.map +1 -0
  236. package/dist/utility/logging/logRotator.d.ts +1 -1
  237. package/dist/utility/logging/logRotator.js +172 -92
  238. package/dist/utility/logging/logRotator.js.map +1 -1
  239. package/dist/utility/npmUtilities.js +6 -4
  240. package/dist/utility/npmUtilities.js.map +1 -1
  241. package/dist/utility/watcherFallback.d.ts +0 -45
  242. package/dist/utility/watcherFallback.js +1 -125
  243. package/dist/utility/watcherFallback.js.map +1 -1
  244. package/dist/validation/configValidator.js +6 -3
  245. package/dist/validation/configValidator.js.map +1 -1
  246. package/index.ts +9 -0
  247. package/json/systemSchema.json +3 -0
  248. package/npm-shrinkwrap.json +177 -87
  249. package/package.json +13 -5
  250. package/resources/DESIGN.md +689 -19
  251. package/resources/DatabaseTransaction.ts +230 -17
  252. package/resources/LMDBTransaction.ts +21 -3
  253. package/resources/PrimaryRocksDatabase.ts +20 -7
  254. package/resources/RecordEncoder.ts +52 -10
  255. package/resources/Resource.ts +97 -13
  256. package/resources/ResourceInterface.ts +8 -0
  257. package/resources/RocksIndexStore.ts +2 -1
  258. package/resources/RocksTransactionLogStore.ts +111 -31
  259. package/resources/Table.ts +1273 -396
  260. package/resources/analytics/DESIGN.md +38 -0
  261. package/resources/analytics/write.ts +50 -17
  262. package/resources/auditStore.ts +460 -11
  263. package/resources/crdt.ts +22 -0
  264. package/resources/dataLoader.ts +3 -4
  265. package/resources/databases.ts +611 -147
  266. package/resources/derivedIndexRegistry.ts +56 -0
  267. package/resources/derivedIndexRuntime.ts +2292 -0
  268. package/resources/graphql.ts +3 -2
  269. package/resources/indexes/DESIGN.md +833 -0
  270. package/resources/indexes/HierarchicalNavigableSmallWorld.ts +924 -46
  271. package/resources/indexes/fullTextDerivedIndex.ts +1165 -0
  272. package/resources/indexes/fullTextNativeBinding.ts +146 -0
  273. package/resources/indexes/hnswDerivedIndex.ts +531 -0
  274. package/resources/indexes/hnswPlaneBinding.ts +174 -0
  275. package/resources/indexes/nativeFullTextDerivedIndexLifecycle.ts +181 -0
  276. package/resources/nodeIdMapping.ts +50 -0
  277. package/resources/record-locks.md +1407 -0
  278. package/resources/recordLock.ts +173 -7
  279. package/resources/recordLockCoordinator.ts +3082 -0
  280. package/resources/replayLogs.ts +5 -0
  281. package/resources/replicatedApplyFailure.ts +77 -0
  282. package/resources/scheduler/DESIGN.md +40 -0
  283. package/resources/scheduler/scheduler.ts +4 -4
  284. package/resources/search.ts +169 -49
  285. package/resources/tracked.ts +73 -22
  286. package/resources/transactionBroadcast.ts +4 -4
  287. package/security/DESIGN.md +175 -0
  288. package/security/auth.ts +53 -24
  289. package/security/deferredAuthentication.ts +24 -2
  290. package/security/jsLoader.ts +9 -4
  291. package/server/DESIGN.md +275 -0
  292. package/server/REST.ts +42 -6
  293. package/server/http.ts +34 -2
  294. package/server/mqtt.ts +6 -4
  295. package/server/serverHelpers/Headers.ts +5 -1
  296. package/server/serverHelpers/NodeAdapterResponse.ts +221 -0
  297. package/server/serverHelpers/Request.ts +33 -131
  298. package/server/serverHelpers/contentTypes.ts +188 -15
  299. package/server/serverHelpers/serverUtilities.ts +157 -27
  300. package/server/serverHelpers/uwsServer.ts +4 -1
  301. package/server/serverHelpers/webSocketCloseReason.ts +25 -0
  302. package/server/storageReclamation.ts +2 -2
  303. package/server/threads/isolatedApplications.ts +157 -0
  304. package/server/threads/logRotationTransport.ts +40 -0
  305. package/server/threads/manageThreads.js +254 -12
  306. package/server/threads/socketRouter.ts +217 -11
  307. package/server/threads/threadServer.js +30 -7
  308. package/studio/web/assets/{Chat-BnCBegQz.js → Chat-DADFFGe_.js} +1 -1
  309. package/studio/web/assets/{FloatingChat-CoDW1ySS.js → FloatingChat-D_mI-rZ7.js} +3 -3
  310. package/studio/web/assets/{apiToken-Bwk5BLXW.js → apiToken-c2NiSDHa.js} +1 -1
  311. package/studio/web/assets/{applications-DHxGi7JH.js → applications-DktUqh7G.js} +1 -1
  312. package/studio/web/assets/{cssMode-s0cWI-_M.js → cssMode-Cs_75Xhw.js} +1 -1
  313. package/studio/web/assets/{editor-DNcRHK54.js → editor-19b-Y1IN.js} +1 -1
  314. package/studio/web/assets/{html-Bdssedlg.js → html-DiYEQMpB.js} +1 -1
  315. package/studio/web/assets/{htmlMode-CoDlJ3fw.js → htmlMode-CmR0y7P_.js} +1 -1
  316. package/studio/web/assets/{index-D6sxmFLR.js → index-Dm0rfkJ7.js} +5 -5
  317. package/studio/web/assets/{index.lazy-tmU5BS8s.js → index.lazy-7vqt2CC3.js} +1 -1
  318. package/studio/web/assets/{javascript-B8meVSTH.js → javascript-BWtCFuOt.js} +1 -1
  319. package/studio/web/assets/{jsonMode-DpIPd35T.js → jsonMode-Buzzbv9y.js} +1 -1
  320. package/studio/web/assets/{languageServices-C_5FMJzQ.js → languageServices-SqsFWfTM.js} +1 -1
  321. package/studio/web/assets/{lspLanguageFeatures-BIzNBzPK.js → lspLanguageFeatures-EMV5cmjo.js} +1 -1
  322. package/studio/web/assets/{notifications-CvZivSbh.js → notifications-CAB-LZWT.js} +1 -1
  323. package/studio/web/assets/{notifications-CQf18QKb.js → notifications-DRzmSRxM.js} +1 -1
  324. package/studio/web/assets/{profile-DdOwtntb.js → profile-BNKAl79n.js} +1 -1
  325. package/studio/web/assets/{regions-n69fwagr.js → regions-CUow_Zw2.js} +1 -1
  326. package/studio/web/assets/{register-PfWTCXWB.js → register-Dkt3WUMp.js} +2 -2
  327. package/studio/web/assets/{setComponentFile-Bg6O7X0S.js → setComponentFile-BZRfMD0N.js} +1 -1
  328. package/studio/web/assets/{setup-CUx_aUDl.js → setup-D_yiEPO2.js} +2 -2
  329. package/studio/web/assets/{status-D7BVKqX9.js → status-DhHh1Ge-.js} +1 -1
  330. package/studio/web/assets/{toggleHighContrast-DBSyXzMr.js → toggleHighContrast-D7L1PDtV.js} +1 -1
  331. package/studio/web/assets/{tsMode-BByKCjBS.js → tsMode-CCwLk1YS.js} +1 -1
  332. package/studio/web/assets/{typescript-DDLnLpw9.js → typescript-BP1j1mjn.js} +1 -1
  333. package/studio/web/assets/{useEntityRestURL-31CHGaHk.js → useEntityRestURL-D7bnYxLw.js} +1 -1
  334. package/studio/web/assets/{workers-pR3jRY9D.js → workers-tOuCNT17.js} +1 -1
  335. package/studio/web/assets/{xml-2iRnMhQO.js → xml-BSG_3mQT.js} +1 -1
  336. package/studio/web/assets/{yaml-Bf92gJpd.js → yaml-DxiLprBB.js} +1 -1
  337. package/studio/web/index.html +1 -1
  338. package/utility/DESIGN.md +55 -0
  339. package/utility/errors/hdbError.ts +54 -0
  340. package/utility/hdbTerms.ts +2 -0
  341. package/utility/logging/harper_logger.ts +210 -30
  342. package/utility/logging/logGenerationCoordinator.ts +196 -0
  343. package/utility/logging/logRotation.ts +367 -0
  344. package/utility/logging/logRotator.ts +196 -91
  345. package/utility/npmUtilities.ts +6 -4
  346. package/utility/watcherFallback.ts +0 -122
  347. package/validation/configValidator.ts +6 -3
@@ -0,0 +1,221 @@
1
+ import { STATUS_CODES } from 'node:http';
2
+ import type {
3
+ IncomingMessage as NodeIncomingMessage,
4
+ OutgoingHttpHeader,
5
+ OutgoingHttpHeaders,
6
+ ServerResponse as NodeServerResponse,
7
+ } from 'node:http';
8
+ import type { Socket } from 'node:net';
9
+ import { PassThrough } from 'node:stream';
10
+ import { Headers as ResponseHeaders, applyWriteHeadHeaders } from './Headers.ts';
11
+
12
+ export interface AdaptedResponse {
13
+ status: number;
14
+ headers: ResponseHeaders;
15
+ body: PassThrough;
16
+ }
17
+
18
+ class HeadersSentError extends Error {
19
+ code = 'ERR_HTTP_HEADERS_SENT';
20
+ constructor(action: string) {
21
+ super(`Cannot ${action} headers after they are sent to the client`);
22
+ }
23
+ }
24
+
25
+ class UnsupportedResponseMethodError extends Error {
26
+ constructor(method: string) {
27
+ super(`${method}() is not supported on a withNodeAdapter() response`);
28
+ }
29
+ }
30
+
31
+ const ignoreError = () => {};
32
+
33
+ export class NodeAdapterResponse extends PassThrough implements NodeServerResponse {
34
+ statusCode = 200;
35
+ statusMessage = '';
36
+ strictContentLength = false;
37
+ chunkedEncoding = false;
38
+ shouldKeepAlive = true;
39
+ useChunkedEncodingByDefault = true;
40
+ sendDate = true;
41
+ readonly req: NodeIncomingMessage;
42
+ readonly socket: Socket | null;
43
+ #headers = new ResponseHeaders();
44
+ #committedStatus: number | undefined;
45
+ #headerText: string | undefined;
46
+ #nodeResponse: NodeServerResponse | undefined;
47
+ #resolve: (response: AdaptedResponse) => void;
48
+ #reject: (reason: unknown) => void;
49
+
50
+ constructor(
51
+ req: NodeIncomingMessage,
52
+ nodeResponse: NodeServerResponse | undefined,
53
+ resolve: (response: AdaptedResponse) => void,
54
+ reject: (reason: unknown) => void
55
+ ) {
56
+ super();
57
+ this.req = req;
58
+ this.socket = req.socket ?? null;
59
+ this.#nodeResponse = nodeResponse;
60
+ this.#resolve = resolve;
61
+ this.#reject = reject;
62
+ this.on('error', ignoreError);
63
+ if (typeof nodeResponse?.on === 'function') {
64
+ const forward = (...args: any[]) => this.emit('timeout', ...args);
65
+ let forwarding = false;
66
+ const syncForwarding = (wanted: boolean) => {
67
+ if (wanted === forwarding) return;
68
+ forwarding = wanted;
69
+ if (wanted) nodeResponse.on('timeout', forward);
70
+ else nodeResponse.removeListener('timeout', forward);
71
+ };
72
+ const onNewListener = (event: string | symbol) => event === 'timeout' && syncForwarding(true);
73
+ const onRemoveListener = (event: string | symbol) =>
74
+ event === 'timeout' && syncForwarding(this.listenerCount('timeout') > 0);
75
+ this.on('newListener', onNewListener);
76
+ this.on('removeListener', onRemoveListener);
77
+ this.once('close', () => {
78
+ syncForwarding(false);
79
+ this.removeListener('newListener', onNewListener);
80
+ this.removeListener('removeListener', onRemoveListener);
81
+ });
82
+ }
83
+ }
84
+
85
+ get headersSent() {
86
+ return this.#committedStatus !== undefined;
87
+ }
88
+ get finished() {
89
+ return this.writableEnded;
90
+ }
91
+ get connection() {
92
+ return this.socket;
93
+ }
94
+ get _header(): string | null {
95
+ if (this.#committedStatus === undefined) return null;
96
+ if (this.#headerText === undefined) {
97
+ let text = `HTTP/1.1 ${this.#committedStatus} ${this.statusMessage}\r\n`;
98
+ for (const [name, value] of this.#headers) {
99
+ if (Array.isArray(value)) for (const entry of value) text += `${name}: ${entry}\r\n`;
100
+ else text += `${name}: ${value}\r\n`;
101
+ }
102
+ this.#headerText = text + '\r\n';
103
+ }
104
+ return this.#headerText;
105
+ }
106
+
107
+ setHeader(name: string, value: number | string | readonly string[]) {
108
+ if (this.headersSent) throw new HeadersSentError('set');
109
+ this.#headers.set(name, value);
110
+ return this;
111
+ }
112
+ setHeaders(headers: Headers | Map<string, number | string | readonly string[]> | ResponseHeaders) {
113
+ let cookies: string[] | undefined;
114
+ for (const [name, value] of headers) {
115
+ if (name.toLowerCase() !== 'set-cookie') this.setHeader(name, value);
116
+ else if (Array.isArray(value)) (cookies ??= []).push(...value);
117
+ else (cookies ??= []).push(String(value));
118
+ }
119
+ if (cookies) this.setHeader('set-cookie', cookies);
120
+ return this;
121
+ }
122
+ appendHeader(name: string, value: string | readonly string[]) {
123
+ if (this.headersSent) throw new HeadersSentError('append');
124
+ if (Array.isArray(value)) for (const entry of value) this.#headers.append(name, entry);
125
+ else this.#headers.append(name, value);
126
+ return this;
127
+ }
128
+ getHeader(name: string) {
129
+ return this.#headers.get(name);
130
+ }
131
+ getHeaders() {
132
+ const headers: OutgoingHttpHeaders = Object.create(null);
133
+ for (const [name, value] of this.#headers) headers[name.toLowerCase()] = value;
134
+ return headers;
135
+ }
136
+ getHeaderNames() {
137
+ return [...this.#headers.keys()];
138
+ }
139
+ hasHeader(name: string) {
140
+ return this.#headers.has(name);
141
+ }
142
+ removeHeader(name: string) {
143
+ if (this.headersSent) throw new HeadersSentError('remove');
144
+ this.#headers.delete(name);
145
+ }
146
+
147
+ writeHead(statusCode: number, statusMessage?: string, headers?: OutgoingHttpHeaders | OutgoingHttpHeader[]): this;
148
+ writeHead(statusCode: number, headers?: OutgoingHttpHeaders | OutgoingHttpHeader[]): this;
149
+ writeHead(
150
+ statusCode: number,
151
+ statusMessageOrHeaders?: string | OutgoingHttpHeaders | OutgoingHttpHeader[],
152
+ headers?: OutgoingHttpHeaders | OutgoingHttpHeader[]
153
+ ) {
154
+ if (this.headersSent) throw new HeadersSentError('write');
155
+ this.statusCode = statusCode;
156
+ if (typeof statusMessageOrHeaders === 'string') this.statusMessage = statusMessageOrHeaders;
157
+ else {
158
+ this.statusMessage ||= STATUS_CODES[statusCode] || 'unknown';
159
+ // writeHead(302, undefined, headers) is the three-argument form with the status message
160
+ // omitted; the hole must not erase the headers the third argument carries.
161
+ headers ??= statusMessageOrHeaders;
162
+ }
163
+ if (headers) applyWriteHeadHeaders(this, headers);
164
+ this.#committedStatus = statusCode;
165
+ this.#resolve({ status: statusCode, headers: this.#headers, body: this });
166
+ return this;
167
+ }
168
+ _implicitHeader() {
169
+ this.writeHead(this.statusCode);
170
+ }
171
+ flushHeaders() {
172
+ if (!this.headersSent) this._implicitHeader();
173
+ }
174
+
175
+ write(
176
+ chunk: unknown,
177
+ encoding?: BufferEncoding | ((error?: Error | null) => void),
178
+ callback?: (error?: Error | null) => void
179
+ ) {
180
+ if (!this.headersSent) this._implicitHeader();
181
+ return super.write(chunk, encoding as BufferEncoding, callback);
182
+ }
183
+ end(chunk?: unknown, encoding?: BufferEncoding | (() => void), callback?: () => void) {
184
+ if (!this.headersSent) this._implicitHeader();
185
+ return super.end(chunk, encoding as BufferEncoding, callback);
186
+ }
187
+ _destroy(error: Error | null, callback: (error?: Error | null) => void) {
188
+ if (!this.headersSent) this.#reject(error ?? new Error('Response destroyed before headers were sent'));
189
+ callback(error);
190
+ }
191
+
192
+ setTimeout(msecs: number, callback?: () => void) {
193
+ if (callback) this.on('timeout', callback);
194
+ const nodeResponse = this.#nodeResponse;
195
+ if (typeof nodeResponse?.setTimeout !== 'function') return this;
196
+ nodeResponse.setTimeout(msecs);
197
+ return this;
198
+ }
199
+ writeContinue(callback?: () => void) {
200
+ if (typeof this.#nodeResponse?.writeContinue === 'function') this.#nodeResponse.writeContinue(callback);
201
+ else callback?.();
202
+ }
203
+ writeProcessing(callback?: () => void) {
204
+ if (typeof this.#nodeResponse?.writeProcessing === 'function') this.#nodeResponse.writeProcessing(callback);
205
+ else callback?.();
206
+ }
207
+ writeEarlyHints(hints: Record<string, string | string[]>, callback?: () => void) {
208
+ if (typeof this.#nodeResponse?.writeEarlyHints === 'function') this.#nodeResponse.writeEarlyHints(hints, callback);
209
+ else callback?.();
210
+ }
211
+ // Trailers need chunked encoding on the wire, which Harper's response layer owns; dropping one silently (a Digest, say) is worse than failing.
212
+ addTrailers(): never {
213
+ throw new UnsupportedResponseMethodError('addTrailers');
214
+ }
215
+ assignSocket(): never {
216
+ throw new UnsupportedResponseMethodError('assignSocket');
217
+ }
218
+ detachSocket(): never {
219
+ throw new UnsupportedResponseMethodError('detachSocket');
220
+ }
221
+ }
@@ -1,10 +1,11 @@
1
1
  import type { IncomingMessage as NodeIncomingMessage, ServerResponse as NodeServerResponse } from 'node:http';
2
2
  import type { Socket } from 'node:net';
3
3
  import { TLSSocket } from 'node:tls';
4
- import { EventEmitter } from 'node:events';
5
- import { Readable, PassThrough } from 'node:stream';
4
+ import { Readable } from 'node:stream';
6
5
  import { Headers as ResponseHeaders } from './Headers.ts';
6
+ import { NodeAdapterResponse, type AdaptedResponse } from './NodeAdapterResponse.ts';
7
7
  import type { ConnectionInfo } from './proxyProtocol.ts';
8
+ import harperLogger from '../../utility/logging/harper_logger.ts';
8
9
 
9
10
  export const isBun = typeof globalThis.Bun !== 'undefined';
10
11
 
@@ -178,10 +179,8 @@ export class Request {
178
179
  * status, headers, and body, resolving the returned promise as soon as headers are available with a
179
180
  * streaming body that can be piped back through the Harper middleware chain.
180
181
  *
181
- * **Important:** The resolved `body` PassThrough must have an `error` listener attached (or be piped
182
- * to a destination that handles errors) before it is consumed. If the underlying connection is reset
183
- * after headers are sent, the body stream is destroyed with an error — without a listener, Node.js
184
- * will throw an uncaught exception.
182
+ * Consume the returned body with `pipeline()`, `finished()` or async iteration so stored stream errors
183
+ * and premature closes are reported.
185
184
  *
186
185
  * Example:
187
186
  * server.http((request, next) =>
@@ -190,11 +189,13 @@ export class Request {
190
189
  */
191
190
  withNodeAdapter(
192
191
  handler: (request: NodeIncomingMessage, response: NodeServerResponse) => void | Promise<void>
193
- ): Promise<{ status: number; headers: ResponseHeaders; body: PassThrough }> {
194
- // Flat headers object matching IncomingMessage.headers format (lowercase keys)
195
- const reqHeaders: Record<string, string | string[]> = Object.create(null);
192
+ ): Promise<AdaptedResponse> {
193
+ const reqHeaders: Record<string, string | string[]> = {};
196
194
  for (const [key, value] of this.headers) {
197
- reqHeaders[key.toLowerCase()] = value;
195
+ const lowerKey = key.toLowerCase();
196
+ if (lowerKey === '__proto__')
197
+ Object.defineProperty(reqHeaders, lowerKey, { value, configurable: true, enumerable: true, writable: true });
198
+ else reqHeaders[lowerKey] = value;
198
199
  }
199
200
 
200
201
  // Proxy the underlying IncomingMessage so body streaming works, but expose
@@ -209,131 +210,32 @@ export class Request {
209
210
  },
210
211
  }) as NodeIncomingMessage;
211
212
 
212
- let resolveResponse!: (value: { status: number; headers: ResponseHeaders; body: PassThrough }) => void;
213
- let rejectResponse!: (reason: unknown) => void;
214
- const response = new Promise<{ status: number; headers: ResponseHeaders; body: PassThrough }>((resolve, reject) => {
215
- resolveResponse = resolve;
216
- rejectResponse = reject;
213
+ let nodeRes!: NodeAdapterResponse;
214
+ const response = new Promise<AdaptedResponse>((resolve, reject) => {
215
+ nodeRes = new NodeAdapterResponse(nodeReq, this._nodeResponse, resolve, reject);
217
216
  });
218
217
 
219
- const responseBody = new PassThrough();
220
- const capturedHeaders = new ResponseHeaders();
221
- let headersFlushed = false;
222
- let nodeRes: ReturnType<typeof Object.assign> & NodeServerResponse;
218
+ const signal = this.signal;
219
+ const onAbort = () => nodeRes.destroy(nodeRes.headersSent ? undefined : signal.reason);
220
+ if (signal.aborted) onAbort();
221
+ else {
222
+ signal.addEventListener('abort', onAbort, { once: true });
223
+ nodeRes.once('close', () => signal.removeEventListener('abort', onAbort));
224
+ }
223
225
 
224
- const flushHeaders = () => {
225
- if (!headersFlushed) {
226
- headersFlushed = true;
227
- nodeRes.headersSent = true;
228
- resolveResponse({ status: nodeRes.statusCode as number, headers: capturedHeaders, body: responseBody });
229
- }
226
+ const onHandlerFailure = (error: Error) => {
227
+ if (!nodeRes.writableEnded) nodeRes.destroy(error);
228
+ else harperLogger.warn('withNodeAdapter handler failed after ending its response', error);
230
229
  };
231
-
232
- const applyHeaders = (hdrs: object | unknown[]) => {
233
- if (Array.isArray(hdrs)) {
234
- if (hdrs.length > 0 && Array.isArray(hdrs[0])) {
235
- for (const [name, value] of hdrs as [string, string][]) capturedHeaders.set(name, value);
236
- } else {
237
- for (let i = 0; i < hdrs.length; i += 2) capturedHeaders.set(hdrs[i] as string, hdrs[i + 1] as string);
238
- }
239
- } else {
240
- for (const [k, v] of Object.entries(hdrs)) capturedHeaders.set(k, v as string);
241
- }
242
- };
243
-
244
- nodeRes = Object.assign(new EventEmitter(), {
245
- statusCode: 200 as number,
246
- statusMessage: '',
247
- headersSent: false,
248
- writable: true,
249
- writableEnded: false,
250
- writableFinished: false,
251
- socket: this._nodeRequest.socket,
252
-
253
- setHeader(name: string, value: string | number | string[]) {
254
- if (Array.isArray(value)) {
255
- // Use set() for first value (overwrites any existing entry) then append() for
256
- // the rest so multiple values — critical for Set-Cookie — are preserved as an
257
- // array rather than collapsed into a single comma-joined string.
258
- if (value.length > 0) {
259
- capturedHeaders.set(name, String(value[0]));
260
- for (let i = 1; i < value.length; i++) capturedHeaders.append(name, String(value[i]));
261
- }
262
- } else {
263
- capturedHeaders.set(name, String(value));
264
- }
265
- return nodeRes;
266
- },
267
- getHeader(name: string) {
268
- return capturedHeaders.get(name);
269
- },
270
- getHeaders() {
271
- return Object.fromEntries(capturedHeaders);
272
- },
273
- hasHeader(name: string) {
274
- return capturedHeaders.has(name);
275
- },
276
- removeHeader(name: string) {
277
- capturedHeaders.delete(name);
278
- },
279
- flushHeaders() {
280
- flushHeaders();
281
- },
282
- writeHead(statusCode: number, statusMessageOrHeaders?: string | object, maybeHeaders?: object) {
283
- if (headersFlushed) return nodeRes;
284
- nodeRes.statusCode = statusCode;
285
- const hdrs = typeof statusMessageOrHeaders === 'string' ? maybeHeaders : statusMessageOrHeaders;
286
- if (hdrs) applyHeaders(hdrs as object | unknown[]);
287
- flushHeaders();
288
- return nodeRes;
289
- },
290
- write(
291
- chunk: unknown,
292
- encoding?: BufferEncoding | ((error?: Error | null) => void),
293
- callback?: (error?: Error | null) => void
294
- ) {
295
- flushHeaders();
296
- if (typeof encoding === 'function') return responseBody.write(chunk as any, encoding);
297
- return responseBody.write(chunk as any, encoding, callback);
298
- },
299
- end(chunk?: unknown, encoding?: BufferEncoding | (() => void), callback?: () => void) {
300
- flushHeaders();
301
- nodeRes.writableEnded = true;
302
- if (typeof chunk === 'function') responseBody.end(chunk as () => void);
303
- else if (typeof encoding === 'function') responseBody.end(chunk as any, encoding);
304
- else responseBody.end(chunk as any, encoding, callback);
305
- return nodeRes;
306
- },
307
- destroy(error?: Error) {
308
- if (!headersFlushed) {
309
- if (error) rejectResponse(error);
310
- else rejectResponse(new Error('Response destroyed before headers were sent'));
311
- // The error has been forwarded to the response promise; suppress the
312
- // PassThrough 'error' event for this one destroy call so Node doesn't
313
- // throw due to having no other listeners.
314
- responseBody.once('error', () => {});
315
- }
316
- responseBody.destroy(error);
317
- return nodeRes;
318
- },
319
- }) as unknown as NodeServerResponse;
320
-
321
- responseBody.on('finish', () => {
322
- nodeRes.writableFinished = true;
323
- (nodeRes as unknown as EventEmitter).emit('finish');
324
- });
325
- responseBody.on('drain', () => {
326
- (nodeRes as unknown as EventEmitter).emit('drain');
327
- });
328
- responseBody.on('close', () => {
329
- (nodeRes as unknown as EventEmitter).emit('close');
330
- });
331
-
332
- const handlerResult = handler(nodeReq, nodeRes);
333
- if (handlerResult != null && typeof (handlerResult as unknown as Promise<void>).then === 'function') {
334
- (handlerResult as unknown as Promise<void>).catch((err: unknown) => {
335
- if (!headersFlushed) rejectResponse(err);
336
- });
230
+ let handlerResult: void | Promise<void>;
231
+ try {
232
+ handlerResult = handler(nodeReq, nodeRes);
233
+ } catch (error) {
234
+ onHandlerFailure(error as Error);
235
+ return response;
236
+ }
237
+ if (typeof (handlerResult as Promise<void>)?.then === 'function') {
238
+ Promise.resolve(handlerResult).catch(onHandlerFailure);
337
239
  }
338
240
  return response;
339
241
  }
@@ -10,6 +10,10 @@ import * as envMgr from '../../utility/environment/environmentManager.ts';
10
10
  import { CONFIG_PARAMS } from '../../utility/hdbTerms.ts';
11
11
  import * as YAML from 'yaml';
12
12
  import { logger } from '../../utility/logging/logger.ts';
13
+ import {
14
+ errorToString as harperErrorToString,
15
+ errorForLog as harperErrorForLog,
16
+ } from '../../utility/logging/harper_logger.ts';
13
17
  import { Blob } from '../../resources/blob.ts';
14
18
  // TODO: Only load this if fastify is loaded
15
19
  import fp from 'fastify-plugin';
@@ -17,6 +21,36 @@ import { parseMultipartRequest } from './multipartParser.ts';
17
21
  const SERIALIZATION_BIGINT = envMgr.get(CONFIG_PARAMS.SERIALIZATION_BIGINT) !== false;
18
22
  const JSONStringify = SERIALIZATION_BIGINT ? stringify : JSON.stringify;
19
23
  const JSONParse = SERIALIZATION_BIGINT ? parse : JSON.parse;
24
+ const streamStartup = Symbol('streamStartup');
25
+ const serializedStreamError = Symbol('serializedStreamError');
26
+
27
+ type StreamStartup = {
28
+ result: Promise<{ failed?: true; error?: unknown }>;
29
+ abort: () => void;
30
+ start: () => void;
31
+ cancel?: () => void;
32
+ };
33
+
34
+ function streamErrorRecord(error: any) {
35
+ let name = 'Error';
36
+ let message;
37
+ try {
38
+ const code = error?.code;
39
+ if (typeof code === 'string' || typeof code === 'number') name = String(code);
40
+ else if (typeof error?.constructor?.name === 'string') name = error.constructor.name;
41
+ else if (typeof error?.name === 'string') name = error.name;
42
+ } catch {}
43
+ try {
44
+ if (typeof error?.message === 'string') message = error.message;
45
+ } catch {}
46
+ message ??= harperErrorToString(error);
47
+ const record: { error: string; message: string; status?: number } = { error: name, message };
48
+ try {
49
+ const status = error?.statusCode ?? error?.status;
50
+ if (typeof status === 'number') record.status = status;
51
+ } catch {}
52
+ return record;
53
+ }
20
54
 
21
55
  const PUBLIC_ENCODE_OPTIONS = {
22
56
  useRecords: false,
@@ -103,9 +137,14 @@ mediaTypes.set('text/yaml', {
103
137
  });
104
138
 
105
139
  const ndjsonHandler = {
106
- serializeStream(data: any) {
140
+ serializeStream(data: any, _response?: Response, request?: any) {
107
141
  if (data?.[Symbol.iterator] || data?.[Symbol.asyncIterator]) {
108
- return Readable.from(transformIterable(data, (msg: any) => JSONStringify(msg) + '\n'));
142
+ return errorFrameStream(
143
+ data,
144
+ (msg: any) => JSONStringify(msg) + '\n',
145
+ (error) => JSONStringify({ $harperStreamError: streamErrorRecord(error) }) + '\n',
146
+ request?.method === 'GET'
147
+ );
109
148
  }
110
149
  return JSONStringify(data) + '\n';
111
150
  },
@@ -132,9 +171,14 @@ function serializeSSEData(data: any) {
132
171
 
133
172
  mediaTypes.set('text/event-stream', {
134
173
  // Server-Sent Events (SSE)
135
- serializeStream: function (iterable) {
174
+ serializeStream: function (iterable, _response?: Response, request?: any) {
136
175
  // create a readable stream that we use to stream out events from our subscription
137
- return Readable.from(transformIterable(iterable, this.serialize));
176
+ return errorFrameStream(
177
+ iterable,
178
+ this.serialize,
179
+ (error) => this.serialize({ event: 'harper-error', data: streamErrorRecord(error) }),
180
+ request?.method === 'GET'
181
+ );
138
182
  },
139
183
  serialize: function (message) {
140
184
  if (message.acknowledge) message.acknowledge();
@@ -396,7 +440,12 @@ export function serialize(responseData, request, responseObject) {
396
440
  (responseData[Symbol.iterator] || responseData[Symbol.asyncIterator]) &&
397
441
  serializer.serializer.serializeStream
398
442
  ) {
399
- if (responseData.mapError) {
443
+ if (
444
+ responseData.mapError &&
445
+ serializer.type !== 'application/x-ndjson' &&
446
+ serializer.type !== 'application/ndjson' &&
447
+ serializer.type !== 'text/event-stream'
448
+ ) {
400
449
  // indicate that we want iterator errors to be returned so we can serialize them in a meaningful way, if possible
401
450
  const getColumns = responseData.getColumns;
402
451
  responseData = responseData.mapError((error) => {
@@ -407,9 +456,10 @@ export function serialize(responseData, request, responseObject) {
407
456
  });
408
457
  responseData.getColumns = getColumns;
409
458
  }
410
- let stream = serializer.serializer.serializeStream(responseData, responseObject);
459
+ let stream = serializer.serializer.serializeStream(responseData, responseObject, request);
411
460
  if (canCompress) {
412
461
  responseObject.headers.set('Content-Encoding', 'br');
462
+ const uncompressedStream = stream;
413
463
  stream = stream.pipe(
414
464
  createBrotliCompress({
415
465
  params: {
@@ -421,6 +471,7 @@ export function serialize(responseData, request, responseObject) {
421
471
  },
422
472
  })
423
473
  );
474
+ if (uncompressedStream[streamStartup]) stream[streamStartup] = uncompressedStream[streamStartup];
424
475
  }
425
476
  return stream;
426
477
  }
@@ -623,24 +674,146 @@ function deserializerUnknownType(contentType: ContentType): Deserialize {
623
674
  }
624
675
  }
625
676
 
626
- function transformIterable(iterable, transform) {
677
+ function errorFrameStream(iterable, transform, serializeError, eager: boolean) {
678
+ const transformed = transformIterable(iterable, transform, serializeError, eager);
679
+ const readable = Readable.from(transformed);
680
+ const startup: StreamStartup = transformed[streamStartup];
681
+ startup.cancel = () => readable.destroy();
682
+ readable[streamStartup] = startup;
683
+ return readable;
684
+ }
685
+
686
+ export function discardSerializedStream(stream) {
687
+ stream?.[streamStartup]?.cancel?.();
688
+ stream?.destroy?.();
689
+ }
690
+
691
+ export function waitForStreamStartup(stream) {
692
+ const startup: StreamStartup | undefined = stream?.[streamStartup];
693
+ if (!startup) return;
694
+ startup.start();
695
+ return startup.result.then((result) => {
696
+ if (result.failed) {
697
+ startup.abort();
698
+ discardSerializedStream(stream);
699
+ throw result.error;
700
+ }
701
+ });
702
+ }
703
+
704
+ function transformIterable(iterable, transform, serializeError, eager) {
705
+ let iterator;
706
+ let terminal = false;
707
+ let first = true;
708
+ let started = false;
709
+ let firstStep;
710
+ let startupSettled = false;
711
+ const startupResult = Promise.withResolvers<{ failed?: true; error?: unknown }>();
712
+ const errorDecision = Promise.withResolvers<boolean>();
713
+ let startupTimer;
714
+
715
+ const commit = () => {
716
+ if (startupTimer) clearImmediate(startupTimer);
717
+ if (!startupSettled) {
718
+ startupSettled = true;
719
+ startupResult.resolve({});
720
+ }
721
+ errorDecision.resolve(true);
722
+ };
723
+ const abort = () => {
724
+ if (startupTimer) clearImmediate(startupTimer);
725
+ errorDecision.resolve(false);
726
+ };
727
+ const handleError = async (error) => {
728
+ terminal = true;
729
+ try {
730
+ const returned = iterator.return?.();
731
+ returned?.catch?.(() => {});
732
+ } catch {}
733
+ if (!startupSettled) startupResult.resolve({ failed: true, error });
734
+ if (!startupSettled && !(await errorDecision.promise)) return { done: true };
735
+ logger.warn?.('Error serializing in stream', harperErrorForLog(error));
736
+ return { value: serializeError(error), done: false, [serializedStreamError]: true };
737
+ };
738
+ const transformStep = (step) => {
739
+ if (step?.[serializedStreamError]) return step;
740
+ if (terminal) return { done: true };
741
+ if (step.done) return step;
742
+ try {
743
+ return { value: transform(step.value), done: false };
744
+ } catch (error) {
745
+ return handleError(error);
746
+ }
747
+ };
748
+ const getRawNext = () => {
749
+ if (terminal) return { done: true };
750
+ let step;
751
+ try {
752
+ step = iterator.next();
753
+ } catch (error) {
754
+ return handleError(error);
755
+ }
756
+ if (step.then) return step.then(undefined, handleError);
757
+ return step;
758
+ };
759
+ const getNext = () => {
760
+ if (terminal) return { done: true };
761
+ let step;
762
+ try {
763
+ step = iterator.next();
764
+ } catch (error) {
765
+ return handleError(error);
766
+ }
767
+ if (step.then) return step.then(transformStep, handleError);
768
+ return transformStep(step);
769
+ };
770
+ const start = () => {
771
+ if (started) return;
772
+ started = true;
773
+ iterator = iterable[Symbol.asyncIterator] ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator]();
774
+ firstStep = getRawNext();
775
+ Promise.resolve(firstStep).then(
776
+ () => {
777
+ if (!terminal) commit();
778
+ },
779
+ () => {}
780
+ );
781
+ // After this turn, the HTTP status can no longer wait for a first item without delaying long-lived streams.
782
+ startupTimer = setImmediate(commit);
783
+ };
784
+ if (eager) start();
785
+
627
786
  return {
787
+ [streamStartup]: {
788
+ result: startupResult.promise,
789
+ abort,
790
+ start,
791
+ },
628
792
  [Symbol.asyncIterator]() {
629
- const iterator = iterable[Symbol.asyncIterator] ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator]();
630
793
  return {
631
794
  next() {
632
- const step = iterator.next();
633
- if (step.then) {
634
- // don't transform the terminal sentinel step, whose value is undefined
635
- return step.then((result) => (result.done ? result : { value: transform(result.value), done: false }));
795
+ if (terminal) return { done: true };
796
+ if (first) {
797
+ first = false;
798
+ start();
799
+ const step = firstStep;
800
+ firstStep = undefined;
801
+ if (step.then) return step.then(transformStep);
802
+ return transformStep(step);
636
803
  }
637
- return step.done ? step : { value: transform(step.value), done: false };
804
+ return getNext();
638
805
  },
639
806
  return(value) {
640
- return iterator.return(value);
807
+ terminal = true;
808
+ started = true;
809
+ if (!iterator)
810
+ iterator = iterable[Symbol.asyncIterator]
811
+ ? iterable[Symbol.asyncIterator]()
812
+ : iterable[Symbol.iterator]?.();
813
+ return iterator?.return?.(value) ?? { value, done: true };
641
814
  },
642
815
  throw(error) {
643
- return iterator.throw(error);
816
+ return iterator?.throw?.(error) ?? Promise.reject(error);
644
817
  },
645
818
  };
646
819
  },