@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
@@ -2,12 +2,24 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RootConfigWatcher = void 0;
4
4
  exports.getSharedRootConfigWatcher = getSharedRootConfigWatcher;
5
- const node_fs_1 = require("node:fs");
6
5
  const configUtils_ts_1 = require("./configUtils.js");
6
+ const readConfigFileSync_ts_1 = require("./readConfigFileSync.js");
7
7
  const node_events_1 = require("node:events");
8
- const yaml_1 = require("yaml");
8
+ const parseConfigFile_ts_1 = require("./parseConfigFile.js");
9
9
  const watcherFallback_ts_1 = require("../utility/watcherFallback.js");
10
10
  const watchPath_ts_1 = require("../utility/watchPath.js");
11
+ const harper_logger_ts_1 = require("../utility/logging/harper_logger.js");
12
+ const configReadRetry_ts_1 = require("./configReadRetry.js");
13
+ const watcherArming_ts_1 = require("./watcherArming.js");
14
+ function isMissingFile(error) {
15
+ return !!error && typeof error === 'object' && error.code === 'ENOENT';
16
+ }
17
+ // `harper_logger` imports this module at its own bottom to break their cycle, so a tagged logger
18
+ // built at module scope would run `loggerWithTag()` before `mainLogger` is initialized.
19
+ let taggedLogger;
20
+ function logger() {
21
+ return (taggedLogger ??= (0, harper_logger_ts_1.loggerWithTag)('config-watcher'));
22
+ }
11
23
  let sharedWatcher;
12
24
  /**
13
25
  * The isolate-wide watcher for the root config file. Consumers (logging, models) subscribe to this
@@ -25,28 +37,79 @@ class RootConfigWatcher extends node_events_1.EventEmitter {
25
37
  #usingPolling;
26
38
  #closed;
27
39
  #openCount = 0;
28
- #partialRead;
40
+ #readCount = 0;
41
+ #readRetry = new configReadRetry_ts_1.ConfigReadRetry();
42
+ #armGate = new watcherArming_ts_1.ArmGate();
43
+ // The gate above is chokidar's scan finishing; this is the barrier's gate. A terminal outcome
44
+ // opens it without claiming the watch is armed, so the arming re-read still runs afterwards.
45
+ #barrierOpen = false;
46
+ #configLoaded = false;
47
+ #readyStaged = false;
48
+ #readyEmitted = false;
29
49
  ready;
30
50
  constructor(configFilePath = (0, configUtils_ts_1.getConfigFilePath)()) {
31
51
  super();
32
52
  this.#configFilePath = configFilePath;
33
53
  const watchTarget = (0, watchPath_ts_1.resolveWatchTarget)(this.#configFilePath);
34
54
  this.#watchPath = watchTarget.path;
35
- this.#partialRead = new watcherFallback_ts_1.PartialReadRetry(this.#configFilePath);
36
55
  this.#usingPolling = watchTarget.mustPoll;
37
56
  this.#closed = false;
38
57
  this.ready = (0, node_events_1.once)(this, 'ready');
39
58
  this.#openWatcher();
40
59
  }
41
60
  #openWatcher() {
42
- this.#openCount++;
61
+ const generation = ++this.#openCount;
43
62
  this.#watcher = (0, watcherFallback_ts_1.guardedWatch)(this.#watchPath, {
44
63
  persistent: false,
45
64
  ...(this.#usingPolling ? watcherFallback_ts_1.POLLING_FALLBACK_OPTIONS : {}),
46
65
  })
47
66
  .on('add', this.handleChange.bind(this))
48
67
  .on('change', this.handleChange.bind(this))
49
- .on('error', this.handleError.bind(this));
68
+ .on('error', this.handleError.bind(this))
69
+ // Generation-bound: `#armGate.reset()` runs before the failed watcher is closed, so a
70
+ // `ready` still queued on it would arm the gate on the replacement's behalf and the
71
+ // replacement's own `ready` would then be a no-op — leaving its scan window unre-read.
72
+ .on('ready', () => this.#handleArmed(generation));
73
+ }
74
+ #handleArmed(generation) {
75
+ if (this.#closed || generation !== this.#openCount)
76
+ return;
77
+ this.#armGate.arm(() => this.#markArmed());
78
+ }
79
+ #markArmed() {
80
+ this.#barrierOpen = true;
81
+ // A write that landed while the watch was unarmed was never reported, and a scan that found
82
+ // no file at all reported nothing either, so arming always re-reads rather than publishing
83
+ // what an earlier read staged — or, with no file, staying pending forever.
84
+ this.#read(true);
85
+ // A read that armed the ladder settles `ready` itself, with the newer config.
86
+ if (!this.#readRetry.pending)
87
+ this.#emitReady();
88
+ }
89
+ #emitReady() {
90
+ if (this.#readyEmitted || !this.#barrierOpen || !this.#readyStaged || this.#closed)
91
+ return;
92
+ this.#readyEmitted = true;
93
+ try {
94
+ this.emit('ready', this.#config);
95
+ }
96
+ catch (error) {
97
+ logger().warn('A Harper configuration listener failed', (0, harper_logger_ts_1.errorForLog)(error));
98
+ }
99
+ }
100
+ // `harper_logger.start()` awaits `ready` with no timeout, so every terminal outcome has to
101
+ // settle the barrier. A failed read discards an earlier staged value, but a watcher error keeps
102
+ // it because no later read superseded it. No config is represented by `undefined`, not `{}`:
103
+ // an empty object is a configuration that turns logging off.
104
+ #stageBootFallback(discardStaged = true) {
105
+ if (this.#readyEmitted)
106
+ return;
107
+ if (discardStaged) {
108
+ this.#config = undefined;
109
+ this.#configLoaded = false;
110
+ }
111
+ this.#readyStaged = true;
112
+ this.#emitReady();
50
113
  }
51
114
  // Test-only: simulate the underlying chokidar watcher emitting an error.
52
115
  // Exposed so the polling-fallback path can be exercised without triggering a
@@ -54,15 +117,23 @@ class RootConfigWatcher extends node_events_1.EventEmitter {
54
117
  _simulateWatcherErrorForTests(error) {
55
118
  this.handleError(error);
56
119
  }
57
- // Test-only: whether the watcher has fallen back to polling.
58
120
  get _usingPollingForTests() {
59
121
  return this.#usingPolling;
60
122
  }
61
- // Test-only: number of times the underlying watcher has been (re)opened.
62
123
  get _openCountForTests() {
63
124
  return this.#openCount;
64
125
  }
126
+ // Distinguishes a ladder rung from a watcher event.
127
+ get _readCountForTests() {
128
+ return this.#readCount;
129
+ }
130
+ get _armedForTests() {
131
+ return this.#armGate.armed;
132
+ }
65
133
  handleError(error) {
134
+ // A queued chokidar error can land after close(), which has dropped every listener.
135
+ if (this.#closed)
136
+ return;
66
137
  // See EntryHandler.#handleWatcherError: a lost native watch handle is benign
67
138
  // and must not be surfaced to consumers as a config-watch failure.
68
139
  if ((0, watcherFallback_ts_1.claimLostNativeWatchError)(error))
@@ -74,6 +145,9 @@ class RootConfigWatcher extends node_events_1.EventEmitter {
74
145
  if (!this.#usingPolling) {
75
146
  (0, watcherFallback_ts_1.warnWatcherFallback)(this.#configFilePath);
76
147
  this.#usingPolling = true;
148
+ // The generation that just failed no longer speaks for the watch; the replacement
149
+ // arms on its own scan, and re-reads then as the first one did.
150
+ this.#armGate.reset();
77
151
  // Start close() from a microtask, not directly here, so a synchronous throw
78
152
  // can't escape this 'error' listener as an uncaught exception.
79
153
  Promise.resolve()
@@ -85,55 +159,135 @@ class RootConfigWatcher extends node_events_1.EventEmitter {
85
159
  if (!this.#closed)
86
160
  this.#openWatcher();
87
161
  })
88
- .catch((error) => console.error(`Could not reopen the ${this.#configFilePath} watch on polling:`, error));
162
+ .catch((error) => logger().warn(`Could not reopen the ${this.#configFilePath} watch on polling`, (0, harper_logger_ts_1.errorForLog)(error)));
89
163
  }
164
+ else {
165
+ // Already polling — the replacement failed too, or the watch was polling from
166
+ // construction (`mustPoll`) and never had a fallback to take. Either way the branch
167
+ // above reopens only once, so this is the watch's terminal outcome and the barrier
168
+ // has to settle or `harper_logger.start()` awaits it forever.
169
+ this.#barrierOpen = true;
170
+ this.#stageBootFallback(false);
171
+ }
172
+ return;
173
+ }
174
+ // chokidar may never reach its own `ready` after a scan error, and nothing else would
175
+ // settle the barrier: the error is this read's terminal outcome. The scan is not over
176
+ // though, so the arm gate stays closed and a later `ready` still takes the arming re-read.
177
+ this.#barrierOpen = true;
178
+ this.#stageBootFallback(false);
179
+ // Settling the barrier removed the `error` listener `once(this, 'ready')` attached, and an
180
+ // emit with none left throws the error back into chokidar's dispatch — as does a consumer
181
+ // that throws from its own handler.
182
+ if (this.listenerCount('error') === 0) {
183
+ logger().warn(`The Harper configuration watcher at ${this.#configFilePath} failed`, (0, harper_logger_ts_1.errorForLog)(error));
90
184
  return;
91
185
  }
92
- this.emit('error', error);
186
+ try {
187
+ this.emit('error', error);
188
+ }
189
+ catch (listenerError) {
190
+ logger().warn('A Harper configuration error listener failed', (0, harper_logger_ts_1.errorForLog)(listenerError));
191
+ }
93
192
  }
94
- // See the descriptor-lifetime invariant on atomicWriteFile (DESIGN.md).
95
193
  handleChange() {
194
+ this.#read(true);
195
+ }
196
+ // `harper_logger.start()` awaits `ready` with no timeout and the ladder may be the only thing
197
+ // left to settle it, so until then its timer keeps the thread alive rather than letting it
198
+ // drain and exit mid-boot.
199
+ #schedule() {
200
+ return this.#readRetry.schedule(() => this.#read(false), !this.#readyEmitted);
201
+ }
202
+ #read(waitForLock) {
203
+ // A queued chokidar callback can still land after close(), which has already discarded the
204
+ // config and dropped every listener.
205
+ if (this.#closed)
206
+ return;
207
+ this.#readCount++;
208
+ let data;
209
+ try {
210
+ data = (0, readConfigFileSync_ts_1.readConfigFileSync)(this.#configFilePath, waitForLock);
211
+ }
212
+ catch (error) {
213
+ // A missing file is not a lock — `readConfigFileSync` does not retry it either, and
214
+ // `OptionsWatcher` settles it immediately as the install window. Taking the ladder here
215
+ // would disagree with that and cost `harper_logger.start()` the whole budget on every
216
+ // boot that has no config file (an env-var-only deployment, an empty mounted rootPath).
217
+ if (!isMissingFile(error) && this.#schedule())
218
+ return;
219
+ // A ladder armed by an earlier empty read is spent by the time a rung lands on ENOENT,
220
+ // and every other terminal path clears its deadline.
221
+ this.#readRetry.reset();
222
+ logger().warn(`Unable to read the Harper configuration file at ${this.#configFilePath}` +
223
+ (this.#configLoaded ? ', continuing with the previously loaded configuration' : '; none has been loaded yet'), (0, harper_logger_ts_1.errorForLog)(error));
224
+ this.#stageBootFallback();
225
+ return;
226
+ }
227
+ // See DESIGN.md, "An empty read is a writer mid-write, not an empty config".
228
+ if (!data) {
229
+ if (this.#schedule())
230
+ return;
231
+ logger().warn(`The Harper configuration file at ${this.#configFilePath} is empty`);
232
+ this.#stageBootFallback();
233
+ return;
234
+ }
96
235
  let config;
97
- // Only the read and parse are guarded: a listener that throws must not be mistaken for a
98
- // half-written file and replayed.
99
236
  try {
100
- config = (0, yaml_1.parse)((0, node_fs_1.readFileSync)(this.#configFilePath, 'utf-8'));
237
+ config = (0, parseConfigFile_ts_1.parseConfigFile)(data, this.#configFilePath);
101
238
  }
102
239
  catch (error) {
103
- // A missing file needs no re-read; anything else may be the file being replaced.
104
- if ((0, watcherFallback_ts_1.isPartialReadError)(error))
105
- this.#scheduleReread(error);
240
+ // A read taken mid-write is untrustworthy, not only an empty one: the writer's first
241
+ // `write(2)` can land a prefix of the document, and the event carrying the rest is the one
242
+ // chokidar throttles away. So an unparseable read rides out the same ladder as an empty
243
+ // one, and only a read that parses releases it.
244
+ if (this.#schedule())
245
+ return;
246
+ logger().warn(error.message);
247
+ this.#stageBootFallback();
106
248
  return;
107
249
  }
108
- // A snapshot that does not parse to an object is the other shape a half-written file
109
- // takes: `''`, `'\n'` and a truncated document all yield null, and adopting that would
110
- // hand every consumer a config with nothing in it.
250
+ // The third shape a mid-write read takes, and the only one that parses: a truncated
251
+ // document, a lone `\n`, a file that is nothing but comments all yield `null` rather than
252
+ // throwing, and adopting one hands every consumer a config with nothing in it. Same ladder
253
+ // as the two above, and past it the file is empty rather than mid-write.
111
254
  if (!config || typeof config !== 'object') {
112
- this.#scheduleReread();
255
+ if (this.#schedule())
256
+ return;
257
+ logger().warn(`The Harper configuration file at ${this.#configFilePath} is empty`);
258
+ this.#stageBootFallback();
259
+ return;
260
+ }
261
+ this.#readRetry.reset();
262
+ // Before `ready` goes out there is no prior state to have changed *since*.
263
+ this.#configLoaded = true;
264
+ this.#readyStaged = true;
265
+ if (!this.#readyEmitted) {
266
+ this.#config = config;
267
+ this.#emitReady();
113
268
  return;
114
269
  }
115
- this.#partialRead.settled();
116
270
  try {
117
- if (!this.#config) {
118
- this.#config = config;
119
- this.emit('ready', this.#config);
120
- return;
121
- }
122
271
  this.emit('change', (this.#config = config));
123
272
  }
124
273
  catch (error) {
125
- (0, watcherFallback_ts_1.warnWatcherListenerError)(this.#configFilePath, error);
274
+ logger().warn('A Harper configuration change listener failed', (0, harper_logger_ts_1.errorForLog)(error));
126
275
  }
127
276
  }
128
- #scheduleReread(error) {
129
- if (this.#partialRead.schedule(() => this.handleChange()))
130
- return;
131
- this.#partialRead.gaveUp(error);
132
- }
133
277
  close() {
278
+ // Closing is a terminal outcome too: leaving `ready` pending would hang anything still
279
+ // awaiting the barrier. Through `#emitReady`, so a listener that throws cannot skip the
280
+ // teardown below it and leave the watcher and its arm timer running.
281
+ this.#barrierOpen = true;
282
+ this.#readyStaged = true;
283
+ this.#emitReady();
134
284
  this.#closed = true;
135
- this.#partialRead.cancel();
136
- this.#watcher.close();
285
+ this.#readRetry.cancel();
286
+ this.#armGate.cancel();
287
+ // chokidar's close() is a promise; an unhandled teardown rejection would reach Node as one,
288
+ // on the path whose whole job is to stop caring about this watcher. Same shape as the
289
+ // exhaustion-recovery close above, and as `OptionsWatcher.close`.
290
+ Promise.resolve(this.#watcher.close()).catch(() => { });
137
291
  this.#config = undefined;
138
292
  this.emit('close');
139
293
  this.removeAllListeners();
@@ -1 +1 @@
1
- {"version":3,"file":"RootConfigWatcher.js","sourceRoot":"","sources":["../../config/RootConfigWatcher.ts"],"names":[],"mappings":";;;AAwBA,gEAEC;AAzBD,qCAAuC;AACvC,qDAAqD;AACrD,6CAAiD;AACjD,+BAA6B;AAC7B,sEASuC;AACvC,0DAA6D;AAE7D,IAAI,aAA4C,CAAC;AAEjD;;;;GAIG;AACH,SAAgB,0BAA0B;IACzC,OAAO,CAAC,aAAa,KAAK,IAAI,iBAAiB,EAAE,CAAC,CAAC;AACpD,CAAC;AAED,MAAa,iBAAkB,SAAQ,0BAAY;IAClD,eAAe,CAAS;IACxB,UAAU,CAAS;IACnB,QAAQ,CAAa;IACrB,OAAO,CAAM;IACb,aAAa,CAAU;IACvB,OAAO,CAAU;IACjB,UAAU,GAAW,CAAC,CAAC;IACvB,YAAY,CAAmB;IAC/B,KAAK,CAAiB;IAEtB,YAAY,iBAAyB,IAAA,kCAAiB,GAAE;QACvD,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,eAAe,GAAG,cAAc,CAAC;QACtC,MAAM,WAAW,GAAG,IAAA,iCAAkB,EAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC7D,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,IAAI,CAAC;QACnC,IAAI,CAAC,YAAY,GAAG,IAAI,qCAAgB,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC/D,IAAI,CAAC,aAAa,GAAG,WAAW,CAAC,QAAQ,CAAC;QAC1C,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,KAAK,GAAG,IAAA,kBAAI,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACjC,IAAI,CAAC,YAAY,EAAE,CAAC;IACrB,CAAC;IAED,YAAY;QACX,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,QAAQ,GAAG,IAAA,iCAAY,EAAC,IAAI,CAAC,UAAU,EAAE;YAC7C,UAAU,EAAE,KAAK;YACjB,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,6CAAwB,CAAC,CAAC,CAAC,EAAE,CAAC;SACvD,CAAC;aACA,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aACvC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aAC1C,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED,yEAAyE;IACzE,6EAA6E;IAC7E,kCAAkC;IAClC,6BAA6B,CAAC,KAAc;QAC3C,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC;IAED,6DAA6D;IAC7D,IAAI,qBAAqB;QACxB,OAAO,IAAI,CAAC,aAAa,CAAC;IAC3B,CAAC;IAED,yEAAyE;IACzE,IAAI,kBAAkB;QACrB,OAAO,IAAI,CAAC,UAAU,CAAC;IACxB,CAAC;IAED,WAAW,CAAC,KAAc;QACzB,6EAA6E;QAC7E,mEAAmE;QACnE,IAAI,IAAA,8CAAyB,EAAC,KAAK,CAAC;YAAE,OAAO;QAC7C,IAAI,IAAA,6CAAwB,EAAC,KAAK,CAAC,EAAE,CAAC;YACrC,wEAAwE;YACxE,wEAAwE;YACxE,kDAAkD;YAClD,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;gBACzB,IAAA,wCAAmB,EAAC,IAAI,CAAC,eAAe,CAAC,CAAC;gBAC1C,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;gBAC1B,4EAA4E;gBAC5E,+DAA+D;gBAC/D,OAAO,CAAC,OAAO,EAAE;qBACf,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;qBACjC,KAAK,CAAC,GAAG,EAAE;oBACX,mEAAmE;gBACpE,CAAC,CAAC;qBACD,IAAI,CAAC,GAAG,EAAE;oBACV,IAAI,CAAC,IAAI,CAAC,OAAO;wBAAE,IAAI,CAAC,YAAY,EAAE,CAAC;gBACxC,CAAC,CAAC;qBACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,wBAAwB,IAAI,CAAC,eAAe,oBAAoB,EAAE,KAAK,CAAC,CAAC,CAAC;YAC5G,CAAC;YACD,OAAO;QACR,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC3B,CAAC;IAED,wEAAwE;IACxE,YAAY;QACX,IAAI,MAAM,CAAC;QACX,yFAAyF;QACzF,kCAAkC;QAClC,IAAI,CAAC;YACJ,MAAM,GAAG,IAAA,YAAK,EAAC,IAAA,sBAAY,EAAC,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;QAC7D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,iFAAiF;YACjF,IAAI,IAAA,uCAAkB,EAAC,KAAK,CAAC;gBAAE,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;YAC3D,OAAO;QACR,CAAC;QACD,qFAAqF;QACrF,uFAAuF;QACvF,mDAAmD;QACnD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC3C,IAAI,CAAC,eAAe,EAAE,CAAC;YACvB,OAAO;QACR,CAAC;QACD,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;QAE5B,IAAI,CAAC;YACJ,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACnB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;gBACtB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;gBACjC,OAAO;YACR,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAA,6CAAwB,EAAC,IAAI,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACF,CAAC;IAED,eAAe,CAAC,KAAe;QAC9B,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YAAE,OAAO;QAClE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IAED,KAAK;QACJ,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,MAAM;QACT,OAAO,IAAI,CAAC,OAAO,CAAC;IACrB,CAAC;CACD;AAlID,8CAkIC"}
1
+ {"version":3,"file":"RootConfigWatcher.js","sourceRoot":"","sources":["../../config/RootConfigWatcher.ts"],"names":[],"mappings":";;;AAmCA,gEAEC;AApCD,qDAAqD;AACrD,mEAA6D;AAC7D,6CAAiD;AACjD,6DAAuD;AACvD,sEAMuC;AACvC,0DAA6D;AAC7D,0EAAiF;AACjF,6DAAuD;AACvD,yDAA6C;AAE7C,SAAS,aAAa,CAAC,KAAc;IACpC,OAAO,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ,CAAC;AACnG,CAAC;AAED,iGAAiG;AACjG,wFAAwF;AACxF,IAAI,YAA0D,CAAC;AAC/D,SAAS,MAAM;IACd,OAAO,CAAC,YAAY,KAAK,IAAA,gCAAa,EAAC,gBAAgB,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,IAAI,aAA4C,CAAC;AAEjD;;;;GAIG;AACH,SAAgB,0BAA0B;IACzC,OAAO,CAAC,aAAa,KAAK,IAAI,iBAAiB,EAAE,CAAC,CAAC;AACpD,CAAC;AAED,MAAa,iBAAkB,SAAQ,0BAAY;IAClD,eAAe,CAAS;IACxB,UAAU,CAAS;IACnB,QAAQ,CAAa;IACrB,OAAO,CAAM;IACb,aAAa,CAAU;IACvB,OAAO,CAAU;IACjB,UAAU,GAAW,CAAC,CAAC;IACvB,UAAU,GAAW,CAAC,CAAC;IACvB,UAAU,GAAoB,IAAI,oCAAe,EAAE,CAAC;IACpD,QAAQ,GAAY,IAAI,0BAAO,EAAE,CAAC;IAClC,8FAA8F;IAC9F,6FAA6F;IAC7F,YAAY,GAAY,KAAK,CAAC;IAC9B,aAAa,GAAY,KAAK,CAAC;IAC/B,YAAY,GAAY,KAAK,CAAC;IAC9B,aAAa,GAAY,KAAK,CAAC;IAC/B,KAAK,CAAiB;IAEtB,YAAY,iBAAyB,IAAA,kCAAiB,GAAE;QACvD,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,eAAe,GAAG,cAAc,CAAC;QACtC,MAAM,WAAW,GAAG,IAAA,iCAAkB,EAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC7D,IAAI,CAAC,UAAU,GAAG,WAAW,CAAC,IAAI,CAAC;QACnC,IAAI,CAAC,aAAa,GAAG,WAAW,CAAC,QAAQ,CAAC;QAC1C,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC,KAAK,GAAG,IAAA,kBAAI,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACjC,IAAI,CAAC,YAAY,EAAE,CAAC;IACrB,CAAC;IAED,YAAY;QACX,MAAM,UAAU,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,QAAQ,GAAG,IAAA,iCAAY,EAAC,IAAI,CAAC,UAAU,EAAE;YAC7C,UAAU,EAAE,KAAK;YACjB,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,6CAAwB,CAAC,CAAC,CAAC,EAAE,CAAC;SACvD,CAAC;aACA,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aACvC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aAC1C,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACzC,sFAAsF;YACtF,oFAAoF;YACpF,uFAAuF;aACtF,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC;IACpD,CAAC;IAED,YAAY,CAAC,UAAkB;QAC9B,IAAI,IAAI,CAAC,OAAO,IAAI,UAAU,KAAK,IAAI,CAAC,UAAU;YAAE,OAAO;QAC3D,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,UAAU;QACT,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,4FAA4F;QAC5F,2FAA2F;QAC3F,2EAA2E;QAC3E,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACjB,8EAA8E;QAC9E,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO;YAAE,IAAI,CAAC,UAAU,EAAE,CAAC;IACjD,CAAC;IAED,UAAU;QACT,IAAI,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QAC3F,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC;YACJ,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,EAAE,CAAC,IAAI,CAAC,wCAAwC,EAAE,IAAA,8BAAW,EAAC,KAAK,CAAC,CAAC,CAAC;QAC7E,CAAC;IACF,CAAC;IAED,2FAA2F;IAC3F,gGAAgG;IAChG,6FAA6F;IAC7F,6DAA6D;IAC7D,kBAAkB,CAAC,aAAa,GAAG,IAAI;QACtC,IAAI,IAAI,CAAC,aAAa;YAAE,OAAO;QAC/B,IAAI,aAAa,EAAE,CAAC;YACnB,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;YACzB,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;QAC5B,CAAC;QACD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,UAAU,EAAE,CAAC;IACnB,CAAC;IAED,yEAAyE;IACzE,6EAA6E;IAC7E,kCAAkC;IAClC,6BAA6B,CAAC,KAAc;QAC3C,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC;IAED,IAAI,qBAAqB;QACxB,OAAO,IAAI,CAAC,aAAa,CAAC;IAC3B,CAAC;IAED,IAAI,kBAAkB;QACrB,OAAO,IAAI,CAAC,UAAU,CAAC;IACxB,CAAC;IAED,oDAAoD;IACpD,IAAI,kBAAkB;QACrB,OAAO,IAAI,CAAC,UAAU,CAAC;IACxB,CAAC;IAED,IAAI,cAAc;QACjB,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;IAC5B,CAAC;IAED,WAAW,CAAC,KAAc;QACzB,oFAAoF;QACpF,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,6EAA6E;QAC7E,mEAAmE;QACnE,IAAI,IAAA,8CAAyB,EAAC,KAAK,CAAC;YAAE,OAAO;QAC7C,IAAI,IAAA,6CAAwB,EAAC,KAAK,CAAC,EAAE,CAAC;YACrC,wEAAwE;YACxE,wEAAwE;YACxE,kDAAkD;YAClD,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;gBACzB,IAAA,wCAAmB,EAAC,IAAI,CAAC,eAAe,CAAC,CAAC;gBAC1C,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;gBAC1B,kFAAkF;gBAClF,gEAAgE;gBAChE,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;gBACtB,4EAA4E;gBAC5E,+DAA+D;gBAC/D,OAAO,CAAC,OAAO,EAAE;qBACf,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;qBACjC,KAAK,CAAC,GAAG,EAAE;oBACX,mEAAmE;gBACpE,CAAC,CAAC;qBACD,IAAI,CAAC,GAAG,EAAE;oBACV,IAAI,CAAC,IAAI,CAAC,OAAO;wBAAE,IAAI,CAAC,YAAY,EAAE,CAAC;gBACxC,CAAC,CAAC;qBACD,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAChB,MAAM,EAAE,CAAC,IAAI,CAAC,wBAAwB,IAAI,CAAC,eAAe,mBAAmB,EAAE,IAAA,8BAAW,EAAC,KAAK,CAAC,CAAC,CAClG,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACP,8EAA8E;gBAC9E,oFAAoF;gBACpF,mFAAmF;gBACnF,8DAA8D;gBAC9D,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;gBACzB,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;YAChC,CAAC;YACD,OAAO;QACR,CAAC;QACD,sFAAsF;QACtF,sFAAsF;QACtF,2FAA2F;QAC3F,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC/B,2FAA2F;QAC3F,0FAA0F;QAC1F,oCAAoC;QACpC,IAAI,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACvC,MAAM,EAAE,CAAC,IAAI,CAAC,uCAAuC,IAAI,CAAC,eAAe,SAAS,EAAE,IAAA,8BAAW,EAAC,KAAK,CAAC,CAAC,CAAC;YACxG,OAAO;QACR,CAAC;QACD,IAAI,CAAC;YACJ,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,aAAa,EAAE,CAAC;YACxB,MAAM,EAAE,CAAC,IAAI,CAAC,8CAA8C,EAAE,IAAA,8BAAW,EAAC,aAAa,CAAC,CAAC,CAAC;QAC3F,CAAC;IACF,CAAC;IAED,YAAY;QACX,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClB,CAAC;IAED,8FAA8F;IAC9F,2FAA2F;IAC3F,2BAA2B;IAC3B,SAAS;QACR,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC/E,CAAC;IAED,KAAK,CAAC,WAAoB;QACzB,2FAA2F;QAC3F,qCAAqC;QACrC,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QACzB,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACJ,IAAI,GAAG,IAAA,0CAAkB,EAAC,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,oFAAoF;YACpF,wFAAwF;YACxF,sFAAsF;YACtF,wFAAwF;YACxF,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE;gBAAE,OAAO;YACtD,uFAAuF;YACvF,qDAAqD;YACrD,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YACxB,MAAM,EAAE,CAAC,IAAI,CACZ,mDAAmD,IAAI,CAAC,eAAe,EAAE;gBACxE,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,uDAAuD,CAAC,CAAC,CAAC,4BAA4B,CAAC,EAC9G,IAAA,8BAAW,EAAC,KAAK,CAAC,CAClB,CAAC;YACF,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC1B,OAAO;QACR,CAAC;QACD,6EAA6E;QAC7E,IAAI,CAAC,IAAI,EAAE,CAAC;YACX,IAAI,IAAI,CAAC,SAAS,EAAE;gBAAE,OAAO;YAC7B,MAAM,EAAE,CAAC,IAAI,CAAC,oCAAoC,IAAI,CAAC,eAAe,WAAW,CAAC,CAAC;YACnF,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC1B,OAAO;QACR,CAAC;QACD,IAAI,MAAM,CAAC;QACX,IAAI,CAAC;YACJ,MAAM,GAAG,IAAA,oCAAe,EAAC,IAAI,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;QACtD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,qFAAqF;YACrF,2FAA2F;YAC3F,wFAAwF;YACxF,gDAAgD;YAChD,IAAI,IAAI,CAAC,SAAS,EAAE;gBAAE,OAAO;YAC7B,MAAM,EAAE,CAAC,IAAI,CAAE,KAAe,CAAC,OAAO,CAAC,CAAC;YACxC,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC1B,OAAO;QACR,CAAC;QACD,oFAAoF;QACpF,0FAA0F;QAC1F,2FAA2F;QAC3F,yEAAyE;QACzE,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC3C,IAAI,IAAI,CAAC,SAAS,EAAE;gBAAE,OAAO;YAC7B,MAAM,EAAE,CAAC,IAAI,CAAC,oCAAoC,IAAI,CAAC,eAAe,WAAW,CAAC,CAAC;YACnF,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC1B,OAAO;QACR,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QAExB,2EAA2E;QAC3E,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACzB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;YACtB,IAAI,CAAC,UAAU,EAAE,CAAC;YAClB,OAAO;QACR,CAAC;QAED,IAAI,CAAC;YACJ,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,EAAE,CAAC,IAAI,CAAC,+CAA+C,EAAE,IAAA,8BAAW,EAAC,KAAK,CAAC,CAAC,CAAC;QACpF,CAAC;IACF,CAAC;IAED,KAAK;QACJ,uFAAuF;QACvF,wFAAwF;QACxF,qEAAqE;QACrE,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;QACzB,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QACvB,4FAA4F;QAC5F,sFAAsF;QACtF,kEAAkE;QAClE,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,MAAM;QACT,OAAO,IAAI,CAAC,OAAO,CAAC;IACrB,CAAC;CACD;AAjRD,8CAiRC"}
@@ -0,0 +1,8 @@
1
+ export declare function _setRetryBudgetForTests(ms?: number): void;
2
+ export declare class ConfigReadRetry {
3
+ #private;
4
+ schedule(retry: () => void, holdEventLoop?: boolean): boolean;
5
+ get pending(): boolean;
6
+ reset(): void;
7
+ cancel(): void;
8
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConfigReadRetry = void 0;
4
+ exports._setRetryBudgetForTests = _setRetryBudgetForTests;
5
+ // Why a lock that outlives the reader's budget is retried from a timer, and why both the bound and
6
+ // the backoff are wall clock rather than an attempt count: see "Root config watchers must read
7
+ // synchronously" in DESIGN.md (harper#2191).
8
+ const RETRY_BUDGET_MS = 3_100;
9
+ const INITIAL_DELAY_MS = 100;
10
+ const MAX_DELAY_MS = 1_600;
11
+ let retryBudgetMs = RETRY_BUDGET_MS;
12
+ // Test-only, the read-side twin of `atomicWriteFile`'s `retryBudgetMs` option. A case that only
13
+ // needs the ladder *spent* has no way to get there but wall clock, so without this each one sits
14
+ // out the shipped 3.1 s; a dozen of them cost the unit job 40 s. Called with no argument it
15
+ // restores the shipped value, and `configReadRetry.test.js` still spends that value in full, so
16
+ // shortening it elsewhere cannot hide a change to what ships.
17
+ function _setRetryBudgetForTests(ms = RETRY_BUDGET_MS) {
18
+ retryBudgetMs = ms;
19
+ }
20
+ class ConfigReadRetry {
21
+ #timer;
22
+ #deadline;
23
+ // Captured with the deadline: the backoff is a fraction of the budget the deadline came from,
24
+ // so reading the module value again mid-ladder would mis-scale it if a test had changed it.
25
+ #budgetMs = retryBudgetMs;
26
+ // `holdEventLoop` is for a caller whose boot barrier this ladder is the only thing left to
27
+ // settle: an unref'd timer would let the thread drain and exit mid-boot instead.
28
+ schedule(retry, holdEventLoop = false) {
29
+ this.cancel();
30
+ const now = performance.now();
31
+ if (this.#deadline === undefined) {
32
+ this.#budgetMs = retryBudgetMs;
33
+ this.#deadline = now + this.#budgetMs;
34
+ }
35
+ const remainingMs = this.#deadline - now;
36
+ if (remainingMs <= 0) {
37
+ this.reset();
38
+ return false;
39
+ }
40
+ const elapsedMs = this.#budgetMs - remainingMs;
41
+ const delayMs = Math.min(Math.max(elapsedMs, INITIAL_DELAY_MS), MAX_DELAY_MS, remainingMs);
42
+ this.#timer = setTimeout(retry, delayMs);
43
+ if (!holdEventLoop)
44
+ this.#timer.unref();
45
+ return true;
46
+ }
47
+ get pending() {
48
+ return this.#timer !== undefined;
49
+ }
50
+ reset() {
51
+ this.cancel();
52
+ this.#deadline = undefined;
53
+ }
54
+ cancel() {
55
+ if (this.#timer) {
56
+ clearTimeout(this.#timer);
57
+ this.#timer = undefined;
58
+ }
59
+ }
60
+ }
61
+ exports.ConfigReadRetry = ConfigReadRetry;
62
+ //# sourceMappingURL=configReadRetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configReadRetry.js","sourceRoot":"","sources":["../../config/configReadRetry.ts"],"names":[],"mappings":";;;AAcA,0DAEC;AAhBD,mGAAmG;AACnG,+FAA+F;AAC/F,6CAA6C;AAC7C,MAAM,eAAe,GAAG,KAAK,CAAC;AAC9B,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,YAAY,GAAG,KAAK,CAAC;AAE3B,IAAI,aAAa,GAAG,eAAe,CAAC;AAEpC,gGAAgG;AAChG,iGAAiG;AACjG,4FAA4F;AAC5F,gGAAgG;AAChG,8DAA8D;AAC9D,SAAgB,uBAAuB,CAAC,KAAa,eAAe;IACnE,aAAa,GAAG,EAAE,CAAC;AACpB,CAAC;AAED,MAAa,eAAe;IAC3B,MAAM,CAAkB;IACxB,SAAS,CAAU;IACnB,8FAA8F;IAC9F,4FAA4F;IAC5F,SAAS,GAAW,aAAa,CAAC;IAElC,2FAA2F;IAC3F,iFAAiF;IACjF,QAAQ,CAAC,KAAiB,EAAE,gBAAyB,KAAK;QACzD,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QAC9B,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;YAClC,IAAI,CAAC,SAAS,GAAG,aAAa,CAAC;YAC/B,IAAI,CAAC,SAAS,GAAG,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC;QACvC,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC;QACzC,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;YACtB,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,OAAO,KAAK,CAAC;QACd,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC;QAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,gBAAgB,CAAC,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;QAC3F,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACzC,IAAI,CAAC,aAAa;YAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACxC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,OAAO;QACV,OAAO,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC;IAClC,CAAC;IAED,KAAK;QACJ,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC5B,CAAC;IAED,MAAM;QACL,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;QACzB,CAAC;IACF,CAAC;CACD;AA3CD,0CA2CC"}
@@ -5,19 +5,19 @@ export declare function resolvePath(relativePath: string): string;
5
5
  * @param param
6
6
  */
7
7
  export declare function getConfigPath(param: string): any;
8
- export declare function isStorageExhausted(error: any): boolean;
9
- export declare function persistConfigDuringBoot(artifactPath: string, write: () => void): boolean;
10
- export declare function atomicWriteFile(filePath: any, content: any, { maxRetries, initialDelayMs, maxDelayMs, skipIfUnchanged, }?: {
8
+ type RenameRetryOptions = {
9
+ retryBudgetMs?: number;
11
10
  maxRetries?: number;
12
11
  initialDelayMs?: number;
13
12
  maxDelayMs?: number;
13
+ };
14
+ type AtomicWriteOptions = RenameRetryOptions & {
14
15
  skipIfUnchanged?: boolean;
15
- }): boolean;
16
- export declare function renameWithRetry(fromPath: any, toPath: any, { maxRetries, initialDelayMs, maxDelayMs, }?: {
17
- maxRetries?: number;
18
- initialDelayMs?: number;
19
- maxDelayMs?: number;
20
- }): void;
16
+ };
17
+ export declare function isStorageExhausted(error: any): boolean;
18
+ export declare function persistConfigDuringBoot(artifactPath: string, write: () => void): boolean;
19
+ export declare function atomicWriteFile(filePath: any, content: any, { retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs, skipIfUnchanged, }?: AtomicWriteOptions): boolean;
20
+ export declare function renameWithRetry(fromPath: any, toPath: any, { retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs, }?: RenameRetryOptions): void;
21
21
  /**
22
22
  * Builds the Harper config file using user inputs and default values from defaultConfig.yaml
23
23
  * @param args - any args that the user provided.
@@ -137,3 +137,4 @@ export declare function addConfig(topLevelElement: any, values: any): Promise<vo
137
137
  export declare function deleteConfigFromFile(param: string): void;
138
138
  export declare function getConfigObj(): any;
139
139
  export declare function getFlatConfigObj(): any;
140
+ export {};
@@ -146,21 +146,41 @@ function getConfigPath(param) {
146
146
  return value;
147
147
  return path_1.default.resolve(rootPath, value);
148
148
  }
149
- // Write atomically via temp file + rename so readers don't observe a truncated/empty file.
150
- // Temp path includes randomness so two worker threads in the same process (same pid) writing
151
- // in the same millisecond can't collide on the temp name and then race the rename.
152
- //
149
+ // Write atomically via a randomized temp file + rename so readers do not observe partial content
150
+ // and concurrent workers, which share process.pid, do not collide on a temp path.
153
151
  // Windows has no POSIX-style "replace an open file" semantics: rename() fails with
154
- // EPERM/EACCES while another descriptor is open on the destination. The sleep below blocks the
155
- // calling thread, so this can only ride out a holder that releases without needing that
156
- // thread's event loop. A holder on the calling thread would live exactly as long as the budget,
157
- // which is why config readers must not keep a descriptor on this file open across an event-loop
158
- // turn (RootConfigWatcher.handleChange, OptionsWatcher#handleChange).
159
- const RENAME_RETRY_MAX_ATTEMPTS = 12;
152
+ // EPERM/EACCES/EBUSY while another worker or AV holds the destination open. Root config watchers use
153
+ // readConfigFileSync so this blocking retry cannot wait on a read owned by its own worker.
154
+ // The budget is the wall-clock window of the 12-attempt schedule it replaced
155
+ // (10+20+40+80+160+320+500*6), so it stays a deadline rather than an attempt count without
156
+ // widening the stall: this loop blocks the calling worker's event loop, and `set_configuration`
157
+ // reaches it from a live request thread.
158
+ const RENAME_RETRY_BUDGET_MS = 3_630;
159
+ // Secondary guard only: stops a degenerate zero-delay option set from spinning the whole budget.
160
+ const RENAME_RETRY_MAX_ATTEMPTS = 25;
160
161
  const RENAME_RETRY_INITIAL_DELAY_MS = 10;
161
162
  const RENAME_RETRY_MAX_DELAY_MS = 500;
162
- // Never notified; exists only so Atomics.wait can time out (a synchronous, CPU-idle sleep).
163
+ // Never notified; Atomics.wait uses this only as a CPU-idle synchronous sleep.
163
164
  const renameRetrySleepBuffer = new Int32Array(new SharedArrayBuffer(4));
165
+ // Classified by code alone rather than gated to win32 like the read side: `process.platform` does
166
+ // not answer whether this filesystem can replace an open file — WSL drvfs, CIFS/SMB and Docker
167
+ // Desktop bind mounts all report `linux` and return these codes transiently.
168
+ function isRetryableRenameError(code) {
169
+ return code === 'EPERM' || code === 'EACCES' || code === 'EBUSY';
170
+ }
171
+ function validateRenameRetryOptions({ retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs }) {
172
+ const invalidOption = !Number.isFinite(retryBudgetMs) ||
173
+ retryBudgetMs < 0 ||
174
+ (!Number.isFinite(maxRetries) && maxRetries !== Infinity) ||
175
+ maxRetries < 0 ||
176
+ !Number.isFinite(initialDelayMs) ||
177
+ initialDelayMs < 0 ||
178
+ !Number.isFinite(maxDelayMs) ||
179
+ maxDelayMs < 0;
180
+ if (invalidOption) {
181
+ throw new RangeError('rename retry options must be non-negative numbers');
182
+ }
183
+ }
164
184
  // Linux has no libuv mapping for EDQUOT, so a quota-exhausted write surfaces as
165
185
  // `Unknown system error -122` with an unusable `code`; the numeric errno is the portable signal
166
186
  // (EDQUOT is 122 on Linux, 69 on macOS).
@@ -186,7 +206,9 @@ function persistConfigDuringBoot(artifactPath, write) {
186
206
  }
187
207
  }
188
208
  // Returns true when the file was written, false when an unchanged write was skipped.
189
- function atomicWriteFile(filePath, content, { maxRetries = RENAME_RETRY_MAX_ATTEMPTS, initialDelayMs = RENAME_RETRY_INITIAL_DELAY_MS, maxDelayMs = RENAME_RETRY_MAX_DELAY_MS, skipIfUnchanged = false, } = {}) {
209
+ function atomicWriteFile(filePath, content, { retryBudgetMs = RENAME_RETRY_BUDGET_MS, maxRetries = RENAME_RETRY_MAX_ATTEMPTS, initialDelayMs = RENAME_RETRY_INITIAL_DELAY_MS, maxDelayMs = RENAME_RETRY_MAX_DELAY_MS, skipIfUnchanged = false, } = {}) {
210
+ // Before the temp write, so an option set that can never rename leaves no file behind.
211
+ validateRenameRetryOptions({ retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs });
190
212
  // Opt-in: skipping means no mtime bump, so no watcher event. Only callers that re-derive the
191
213
  // same file every boot want that.
192
214
  if (skipIfUnchanged && matchesFileContent(filePath, content))
@@ -201,19 +223,24 @@ function atomicWriteFile(filePath, content, { maxRetries = RENAME_RETRY_MAX_ATTE
201
223
  throw err;
202
224
  }
203
225
  try {
204
- renameWithRetry(tempPath, filePath, { maxRetries, initialDelayMs, maxDelayMs });
226
+ renameWithRetry(tempPath, filePath, { retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs });
205
227
  }
206
228
  catch (err) {
229
+ // The temp name carries fresh randomness on every call, so a spent budget would otherwise
230
+ // leave a file nothing else will ever collect.
207
231
  removeTempFile(tempPath);
208
232
  throw err;
209
233
  }
210
234
  return true;
211
235
  }
212
- function renameWithRetry(fromPath, toPath, { maxRetries = RENAME_RETRY_MAX_ATTEMPTS, initialDelayMs = RENAME_RETRY_INITIAL_DELAY_MS, maxDelayMs = RENAME_RETRY_MAX_DELAY_MS, } = {}) {
236
+ function renameWithRetry(fromPath, toPath, { retryBudgetMs = RENAME_RETRY_BUDGET_MS, maxRetries = RENAME_RETRY_MAX_ATTEMPTS, initialDelayMs = RENAME_RETRY_INITIAL_DELAY_MS, maxDelayMs = RENAME_RETRY_MAX_DELAY_MS, } = {}) {
237
+ validateRenameRetryOptions({ retryBudgetMs, maxRetries, initialDelayMs, maxDelayMs });
213
238
  let retries = maxRetries;
214
239
  let delayMs = initialDelayMs;
240
+ let retryDeadline;
241
+ let finalAttempt = false;
215
242
  let attempts = 0;
216
- const startedAt = Date.now();
243
+ const startedAt = performance.now();
217
244
  while (true) {
218
245
  try {
219
246
  attempts++;
@@ -221,20 +248,29 @@ function renameWithRetry(fromPath, toPath, { maxRetries = RENAME_RETRY_MAX_ATTEM
221
248
  return;
222
249
  }
223
250
  catch (err) {
224
- if (retries > 0 && (err.code === 'EPERM' || err.code === 'EACCES')) {
251
+ if (!finalAttempt && retries > 0 && isRetryableRenameError(err.code)) {
225
252
  retries--;
226
- // Sleep synchronously (all call sites are sync) to allow the holder to close the
227
- // file. Atomics.wait yields the thread to the OS instead of spinning the CPU,
228
- // which is what makes a multi-second worst-case budget affordable.
229
- if (delayMs > 0)
230
- Atomics.wait(renameRetrySleepBuffer, 0, 0, delayMs);
231
- delayMs = Math.min(delayMs * 2, maxDelayMs);
232
- continue;
253
+ if (retryDeadline === undefined) {
254
+ retryDeadline = performance.now() + retryBudgetMs;
255
+ }
256
+ const remainingBudgetMs = retryDeadline - performance.now();
257
+ if (remainingBudgetMs > 0) {
258
+ // Sleep synchronously (all call sites are sync) to allow the holder to close the
259
+ // file. Atomics.wait yields the thread to the OS instead of spinning the CPU,
260
+ // which is what makes a multi-second worst-case budget affordable.
261
+ const sleepMs = Math.min(delayMs, remainingBudgetMs);
262
+ finalAttempt = sleepMs === remainingBudgetMs;
263
+ if (sleepMs > 0)
264
+ Atomics.wait(renameRetrySleepBuffer, 0, 0, sleepMs);
265
+ delayMs = Math.min(Math.max(delayMs * 2, RENAME_RETRY_INITIAL_DELAY_MS), maxDelayMs);
266
+ continue;
267
+ }
233
268
  }
234
- // Attempts and elapsed distinguish a holder that never released from one that lost
235
- // a race, and neither survives on the rethrown error.
236
- if (err.code === 'EPERM' || err.code === 'EACCES') {
237
- harper_logger_ts_1.default.warn(`Could not replace ${toPath}: ${err.code} after ${attempts} attempts over ${Date.now() - startedAt}ms`);
269
+ // Whether the budget was spent or the code was never retryable is the difference
270
+ // between a holder that never released and a one-off failure, and neither survives on
271
+ // the rethrown error.
272
+ if (isRetryableRenameError(err.code)) {
273
+ harper_logger_ts_1.default.warn(`Could not replace ${toPath}: ${err.code} after ${attempts} attempts over ${Math.round(performance.now() - startedAt)}ms`);
238
274
  }
239
275
  throw err;
240
276
  }