@herdctl/core 0.0.1

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 (520) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test.log +219 -0
  3. package/.turbo/turbo-typecheck.log +4 -0
  4. package/coverage/base.css +224 -0
  5. package/coverage/block-navigation.js +87 -0
  6. package/coverage/coverage-final.json +51 -0
  7. package/coverage/favicon.png +0 -0
  8. package/coverage/index.html +251 -0
  9. package/coverage/prettify.css +1 -0
  10. package/coverage/prettify.js +2 -0
  11. package/coverage/sort-arrow-sprite.png +0 -0
  12. package/coverage/sorter.js +210 -0
  13. package/coverage/src/config/index.html +191 -0
  14. package/coverage/src/config/index.ts.html +442 -0
  15. package/coverage/src/config/interpolate.ts.html +652 -0
  16. package/coverage/src/config/loader.ts.html +1501 -0
  17. package/coverage/src/config/merge.ts.html +823 -0
  18. package/coverage/src/config/parser.ts.html +1213 -0
  19. package/coverage/src/config/schema.ts.html +1123 -0
  20. package/coverage/src/fleet-manager/errors.ts.html +2326 -0
  21. package/coverage/src/fleet-manager/event-types.ts.html +1219 -0
  22. package/coverage/src/fleet-manager/fleet-manager.ts.html +7030 -0
  23. package/coverage/src/fleet-manager/index.html +206 -0
  24. package/coverage/src/fleet-manager/index.ts.html +469 -0
  25. package/coverage/src/fleet-manager/job-manager.ts.html +2074 -0
  26. package/coverage/src/fleet-manager/job-queue.ts.html +2479 -0
  27. package/coverage/src/fleet-manager/types.ts.html +2602 -0
  28. package/coverage/src/index.html +116 -0
  29. package/coverage/src/index.ts.html +181 -0
  30. package/coverage/src/runner/errors.ts.html +1006 -0
  31. package/coverage/src/runner/index.html +191 -0
  32. package/coverage/src/runner/index.ts.html +256 -0
  33. package/coverage/src/runner/job-executor.ts.html +1429 -0
  34. package/coverage/src/runner/message-processor.ts.html +1150 -0
  35. package/coverage/src/runner/sdk-adapter.ts.html +658 -0
  36. package/coverage/src/runner/types.ts.html +559 -0
  37. package/coverage/src/scheduler/errors.ts.html +388 -0
  38. package/coverage/src/scheduler/index.html +206 -0
  39. package/coverage/src/scheduler/index.ts.html +244 -0
  40. package/coverage/src/scheduler/interval.ts.html +652 -0
  41. package/coverage/src/scheduler/schedule-runner.ts.html +1411 -0
  42. package/coverage/src/scheduler/schedule-state.ts.html +718 -0
  43. package/coverage/src/scheduler/scheduler.ts.html +1795 -0
  44. package/coverage/src/scheduler/types.ts.html +733 -0
  45. package/coverage/src/state/directory.ts.html +736 -0
  46. package/coverage/src/state/errors.ts.html +376 -0
  47. package/coverage/src/state/fleet-state.ts.html +937 -0
  48. package/coverage/src/state/index.html +221 -0
  49. package/coverage/src/state/index.ts.html +322 -0
  50. package/coverage/src/state/job-metadata.ts.html +1420 -0
  51. package/coverage/src/state/job-output.ts.html +1033 -0
  52. package/coverage/src/state/schemas/fleet-state.ts.html +445 -0
  53. package/coverage/src/state/schemas/index.html +176 -0
  54. package/coverage/src/state/schemas/index.ts.html +286 -0
  55. package/coverage/src/state/schemas/job-metadata.ts.html +628 -0
  56. package/coverage/src/state/schemas/job-output.ts.html +616 -0
  57. package/coverage/src/state/schemas/session-info.ts.html +361 -0
  58. package/coverage/src/state/session.ts.html +844 -0
  59. package/coverage/src/state/types.ts.html +262 -0
  60. package/coverage/src/state/utils/atomic.ts.html +748 -0
  61. package/coverage/src/state/utils/index.html +146 -0
  62. package/coverage/src/state/utils/index.ts.html +103 -0
  63. package/coverage/src/state/utils/reads.ts.html +1621 -0
  64. package/coverage/src/work-sources/adapters/github.ts.html +3583 -0
  65. package/coverage/src/work-sources/adapters/index.html +131 -0
  66. package/coverage/src/work-sources/adapters/index.ts.html +277 -0
  67. package/coverage/src/work-sources/errors.ts.html +298 -0
  68. package/coverage/src/work-sources/index.html +176 -0
  69. package/coverage/src/work-sources/index.ts.html +529 -0
  70. package/coverage/src/work-sources/manager.ts.html +1324 -0
  71. package/coverage/src/work-sources/registry.ts.html +619 -0
  72. package/coverage/src/work-sources/types.ts.html +568 -0
  73. package/dist/config/__tests__/agent.test.d.ts +2 -0
  74. package/dist/config/__tests__/agent.test.d.ts.map +1 -0
  75. package/dist/config/__tests__/agent.test.js +752 -0
  76. package/dist/config/__tests__/agent.test.js.map +1 -0
  77. package/dist/config/__tests__/interpolate.test.d.ts +2 -0
  78. package/dist/config/__tests__/interpolate.test.d.ts.map +1 -0
  79. package/dist/config/__tests__/interpolate.test.js +509 -0
  80. package/dist/config/__tests__/interpolate.test.js.map +1 -0
  81. package/dist/config/__tests__/loader.test.d.ts +2 -0
  82. package/dist/config/__tests__/loader.test.d.ts.map +1 -0
  83. package/dist/config/__tests__/loader.test.js +631 -0
  84. package/dist/config/__tests__/loader.test.js.map +1 -0
  85. package/dist/config/__tests__/merge.test.d.ts +2 -0
  86. package/dist/config/__tests__/merge.test.d.ts.map +1 -0
  87. package/dist/config/__tests__/merge.test.js +672 -0
  88. package/dist/config/__tests__/merge.test.js.map +1 -0
  89. package/dist/config/__tests__/parser.test.d.ts +2 -0
  90. package/dist/config/__tests__/parser.test.d.ts.map +1 -0
  91. package/dist/config/__tests__/parser.test.js +476 -0
  92. package/dist/config/__tests__/parser.test.js.map +1 -0
  93. package/dist/config/__tests__/schema.test.d.ts +2 -0
  94. package/dist/config/__tests__/schema.test.d.ts.map +1 -0
  95. package/dist/config/__tests__/schema.test.js +776 -0
  96. package/dist/config/__tests__/schema.test.js.map +1 -0
  97. package/dist/config/index.d.ts +11 -0
  98. package/dist/config/index.d.ts.map +1 -0
  99. package/dist/config/index.js +26 -0
  100. package/dist/config/index.js.map +1 -0
  101. package/dist/config/interpolate.d.ts +76 -0
  102. package/dist/config/interpolate.d.ts.map +1 -0
  103. package/dist/config/interpolate.js +143 -0
  104. package/dist/config/interpolate.js.map +1 -0
  105. package/dist/config/loader.d.ts +147 -0
  106. package/dist/config/loader.d.ts.map +1 -0
  107. package/dist/config/loader.js +336 -0
  108. package/dist/config/loader.js.map +1 -0
  109. package/dist/config/merge.d.ts +84 -0
  110. package/dist/config/merge.d.ts.map +1 -0
  111. package/dist/config/merge.js +138 -0
  112. package/dist/config/merge.js.map +1 -0
  113. package/dist/config/parser.d.ts +143 -0
  114. package/dist/config/parser.d.ts.map +1 -0
  115. package/dist/config/parser.js +316 -0
  116. package/dist/config/parser.js.map +1 -0
  117. package/dist/config/schema.d.ts +1906 -0
  118. package/dist/config/schema.d.ts.map +1 -0
  119. package/dist/config/schema.js +268 -0
  120. package/dist/config/schema.js.map +1 -0
  121. package/dist/fleet-manager/__tests__/coverage.test.d.ts +13 -0
  122. package/dist/fleet-manager/__tests__/coverage.test.d.ts.map +1 -0
  123. package/dist/fleet-manager/__tests__/coverage.test.js +2282 -0
  124. package/dist/fleet-manager/__tests__/coverage.test.js.map +1 -0
  125. package/dist/fleet-manager/__tests__/errors.test.d.ts +7 -0
  126. package/dist/fleet-manager/__tests__/errors.test.d.ts.map +1 -0
  127. package/dist/fleet-manager/__tests__/errors.test.js +557 -0
  128. package/dist/fleet-manager/__tests__/errors.test.js.map +1 -0
  129. package/dist/fleet-manager/__tests__/event-helpers.test.d.ts +7 -0
  130. package/dist/fleet-manager/__tests__/event-helpers.test.d.ts.map +1 -0
  131. package/dist/fleet-manager/__tests__/event-helpers.test.js +368 -0
  132. package/dist/fleet-manager/__tests__/event-helpers.test.js.map +1 -0
  133. package/dist/fleet-manager/__tests__/integration.test.d.ts +11 -0
  134. package/dist/fleet-manager/__tests__/integration.test.d.ts.map +1 -0
  135. package/dist/fleet-manager/__tests__/integration.test.js +949 -0
  136. package/dist/fleet-manager/__tests__/integration.test.js.map +1 -0
  137. package/dist/fleet-manager/__tests__/job-control.test.d.ts +7 -0
  138. package/dist/fleet-manager/__tests__/job-control.test.d.ts.map +1 -0
  139. package/dist/fleet-manager/__tests__/job-control.test.js +215 -0
  140. package/dist/fleet-manager/__tests__/job-control.test.js.map +1 -0
  141. package/dist/fleet-manager/__tests__/job-manager.test.d.ts +7 -0
  142. package/dist/fleet-manager/__tests__/job-manager.test.d.ts.map +1 -0
  143. package/dist/fleet-manager/__tests__/job-manager.test.js +659 -0
  144. package/dist/fleet-manager/__tests__/job-manager.test.js.map +1 -0
  145. package/dist/fleet-manager/__tests__/job-queue.test.d.ts +5 -0
  146. package/dist/fleet-manager/__tests__/job-queue.test.d.ts.map +1 -0
  147. package/dist/fleet-manager/__tests__/job-queue.test.js +315 -0
  148. package/dist/fleet-manager/__tests__/job-queue.test.js.map +1 -0
  149. package/dist/fleet-manager/__tests__/reload.test.d.ts +7 -0
  150. package/dist/fleet-manager/__tests__/reload.test.d.ts.map +1 -0
  151. package/dist/fleet-manager/__tests__/reload.test.js +609 -0
  152. package/dist/fleet-manager/__tests__/reload.test.js.map +1 -0
  153. package/dist/fleet-manager/__tests__/status-queries.test.d.ts +7 -0
  154. package/dist/fleet-manager/__tests__/status-queries.test.d.ts.map +1 -0
  155. package/dist/fleet-manager/__tests__/status-queries.test.js +488 -0
  156. package/dist/fleet-manager/__tests__/status-queries.test.js.map +1 -0
  157. package/dist/fleet-manager/__tests__/trigger.test.d.ts +7 -0
  158. package/dist/fleet-manager/__tests__/trigger.test.d.ts.map +1 -0
  159. package/dist/fleet-manager/__tests__/trigger.test.js +471 -0
  160. package/dist/fleet-manager/__tests__/trigger.test.js.map +1 -0
  161. package/dist/fleet-manager/errors.d.ts +407 -0
  162. package/dist/fleet-manager/errors.d.ts.map +1 -0
  163. package/dist/fleet-manager/errors.js +569 -0
  164. package/dist/fleet-manager/errors.js.map +1 -0
  165. package/dist/fleet-manager/event-types.d.ts +302 -0
  166. package/dist/fleet-manager/event-types.d.ts.map +1 -0
  167. package/dist/fleet-manager/event-types.js +9 -0
  168. package/dist/fleet-manager/event-types.js.map +1 -0
  169. package/dist/fleet-manager/fleet-manager.d.ts +699 -0
  170. package/dist/fleet-manager/fleet-manager.d.ts.map +1 -0
  171. package/dist/fleet-manager/fleet-manager.js +1906 -0
  172. package/dist/fleet-manager/fleet-manager.js.map +1 -0
  173. package/dist/fleet-manager/index.d.ts +17 -0
  174. package/dist/fleet-manager/index.d.ts.map +1 -0
  175. package/dist/fleet-manager/index.js +29 -0
  176. package/dist/fleet-manager/index.js.map +1 -0
  177. package/dist/fleet-manager/job-manager.d.ts +271 -0
  178. package/dist/fleet-manager/job-manager.d.ts.map +1 -0
  179. package/dist/fleet-manager/job-manager.js +443 -0
  180. package/dist/fleet-manager/job-manager.js.map +1 -0
  181. package/dist/fleet-manager/job-queue.d.ts +422 -0
  182. package/dist/fleet-manager/job-queue.d.ts.map +1 -0
  183. package/dist/fleet-manager/job-queue.js +448 -0
  184. package/dist/fleet-manager/job-queue.js.map +1 -0
  185. package/dist/fleet-manager/types.d.ts +680 -0
  186. package/dist/fleet-manager/types.d.ts.map +1 -0
  187. package/dist/fleet-manager/types.js +8 -0
  188. package/dist/fleet-manager/types.js.map +1 -0
  189. package/dist/index.d.ts +20 -0
  190. package/dist/index.d.ts.map +1 -0
  191. package/dist/index.js +26 -0
  192. package/dist/index.js.map +1 -0
  193. package/dist/runner/__tests__/errors.test.d.ts +2 -0
  194. package/dist/runner/__tests__/errors.test.d.ts.map +1 -0
  195. package/dist/runner/__tests__/errors.test.js +264 -0
  196. package/dist/runner/__tests__/errors.test.js.map +1 -0
  197. package/dist/runner/__tests__/job-executor.test.d.ts +2 -0
  198. package/dist/runner/__tests__/job-executor.test.d.ts.map +1 -0
  199. package/dist/runner/__tests__/job-executor.test.js +1345 -0
  200. package/dist/runner/__tests__/job-executor.test.js.map +1 -0
  201. package/dist/runner/__tests__/message-processor.test.d.ts +2 -0
  202. package/dist/runner/__tests__/message-processor.test.d.ts.map +1 -0
  203. package/dist/runner/__tests__/message-processor.test.js +768 -0
  204. package/dist/runner/__tests__/message-processor.test.js.map +1 -0
  205. package/dist/runner/__tests__/sdk-adapter.test.d.ts +2 -0
  206. package/dist/runner/__tests__/sdk-adapter.test.d.ts.map +1 -0
  207. package/dist/runner/__tests__/sdk-adapter.test.js +554 -0
  208. package/dist/runner/__tests__/sdk-adapter.test.js.map +1 -0
  209. package/dist/runner/errors.d.ts +121 -0
  210. package/dist/runner/errors.d.ts.map +1 -0
  211. package/dist/runner/errors.js +212 -0
  212. package/dist/runner/errors.js.map +1 -0
  213. package/dist/runner/index.d.ts +12 -0
  214. package/dist/runner/index.d.ts.map +1 -0
  215. package/dist/runner/index.js +15 -0
  216. package/dist/runner/index.js.map +1 -0
  217. package/dist/runner/job-executor.d.ts +98 -0
  218. package/dist/runner/job-executor.d.ts.map +1 -0
  219. package/dist/runner/job-executor.js +333 -0
  220. package/dist/runner/job-executor.js.map +1 -0
  221. package/dist/runner/message-processor.d.ts +45 -0
  222. package/dist/runner/message-processor.d.ts.map +1 -0
  223. package/dist/runner/message-processor.js +294 -0
  224. package/dist/runner/message-processor.js.map +1 -0
  225. package/dist/runner/sdk-adapter.d.ts +60 -0
  226. package/dist/runner/sdk-adapter.d.ts.map +1 -0
  227. package/dist/runner/sdk-adapter.js +138 -0
  228. package/dist/runner/sdk-adapter.js.map +1 -0
  229. package/dist/runner/types.d.ts +135 -0
  230. package/dist/runner/types.d.ts.map +1 -0
  231. package/dist/runner/types.js +7 -0
  232. package/dist/runner/types.js.map +1 -0
  233. package/dist/scheduler/__tests__/errors.test.d.ts +2 -0
  234. package/dist/scheduler/__tests__/errors.test.d.ts.map +1 -0
  235. package/dist/scheduler/__tests__/errors.test.js +101 -0
  236. package/dist/scheduler/__tests__/errors.test.js.map +1 -0
  237. package/dist/scheduler/__tests__/interval.test.d.ts +2 -0
  238. package/dist/scheduler/__tests__/interval.test.d.ts.map +1 -0
  239. package/dist/scheduler/__tests__/interval.test.js +419 -0
  240. package/dist/scheduler/__tests__/interval.test.js.map +1 -0
  241. package/dist/scheduler/__tests__/schedule-runner.test.d.ts +2 -0
  242. package/dist/scheduler/__tests__/schedule-runner.test.d.ts.map +1 -0
  243. package/dist/scheduler/__tests__/schedule-runner.test.js +634 -0
  244. package/dist/scheduler/__tests__/schedule-runner.test.js.map +1 -0
  245. package/dist/scheduler/__tests__/schedule-state.test.d.ts +2 -0
  246. package/dist/scheduler/__tests__/schedule-state.test.d.ts.map +1 -0
  247. package/dist/scheduler/__tests__/schedule-state.test.js +572 -0
  248. package/dist/scheduler/__tests__/schedule-state.test.js.map +1 -0
  249. package/dist/scheduler/__tests__/scheduler.test.d.ts +2 -0
  250. package/dist/scheduler/__tests__/scheduler.test.d.ts.map +1 -0
  251. package/dist/scheduler/__tests__/scheduler.test.js +987 -0
  252. package/dist/scheduler/__tests__/scheduler.test.js.map +1 -0
  253. package/dist/scheduler/errors.d.ts +61 -0
  254. package/dist/scheduler/errors.d.ts.map +1 -0
  255. package/dist/scheduler/errors.js +81 -0
  256. package/dist/scheduler/errors.js.map +1 -0
  257. package/dist/scheduler/index.d.ts +13 -0
  258. package/dist/scheduler/index.d.ts.map +1 -0
  259. package/dist/scheduler/index.js +17 -0
  260. package/dist/scheduler/index.js.map +1 -0
  261. package/dist/scheduler/interval.d.ts +64 -0
  262. package/dist/scheduler/interval.d.ts.map +1 -0
  263. package/dist/scheduler/interval.js +139 -0
  264. package/dist/scheduler/interval.js.map +1 -0
  265. package/dist/scheduler/schedule-runner.d.ts +149 -0
  266. package/dist/scheduler/schedule-runner.d.ts.map +1 -0
  267. package/dist/scheduler/schedule-runner.js +277 -0
  268. package/dist/scheduler/schedule-runner.js.map +1 -0
  269. package/dist/scheduler/schedule-state.d.ts +105 -0
  270. package/dist/scheduler/schedule-state.d.ts.map +1 -0
  271. package/dist/scheduler/schedule-state.js +151 -0
  272. package/dist/scheduler/schedule-state.js.map +1 -0
  273. package/dist/scheduler/scheduler.d.ts +138 -0
  274. package/dist/scheduler/scheduler.d.ts.map +1 -0
  275. package/dist/scheduler/scheduler.js +423 -0
  276. package/dist/scheduler/scheduler.js.map +1 -0
  277. package/dist/scheduler/types.d.ts +160 -0
  278. package/dist/scheduler/types.d.ts.map +1 -0
  279. package/dist/scheduler/types.js +8 -0
  280. package/dist/scheduler/types.js.map +1 -0
  281. package/dist/state/__tests__/directory.test.d.ts +2 -0
  282. package/dist/state/__tests__/directory.test.d.ts.map +1 -0
  283. package/dist/state/__tests__/directory.test.js +414 -0
  284. package/dist/state/__tests__/directory.test.js.map +1 -0
  285. package/dist/state/__tests__/fleet-state.test.d.ts +2 -0
  286. package/dist/state/__tests__/fleet-state.test.d.ts.map +1 -0
  287. package/dist/state/__tests__/fleet-state.test.js +696 -0
  288. package/dist/state/__tests__/fleet-state.test.js.map +1 -0
  289. package/dist/state/__tests__/job-metadata-schema.test.d.ts +2 -0
  290. package/dist/state/__tests__/job-metadata-schema.test.d.ts.map +1 -0
  291. package/dist/state/__tests__/job-metadata-schema.test.js +329 -0
  292. package/dist/state/__tests__/job-metadata-schema.test.js.map +1 -0
  293. package/dist/state/__tests__/job-metadata.test.d.ts +2 -0
  294. package/dist/state/__tests__/job-metadata.test.d.ts.map +1 -0
  295. package/dist/state/__tests__/job-metadata.test.js +667 -0
  296. package/dist/state/__tests__/job-metadata.test.js.map +1 -0
  297. package/dist/state/__tests__/job-output.test.d.ts +2 -0
  298. package/dist/state/__tests__/job-output.test.d.ts.map +1 -0
  299. package/dist/state/__tests__/job-output.test.js +672 -0
  300. package/dist/state/__tests__/job-output.test.js.map +1 -0
  301. package/dist/state/__tests__/session-schema.test.d.ts +2 -0
  302. package/dist/state/__tests__/session-schema.test.d.ts.map +1 -0
  303. package/dist/state/__tests__/session-schema.test.js +323 -0
  304. package/dist/state/__tests__/session-schema.test.js.map +1 -0
  305. package/dist/state/__tests__/session.test.d.ts +2 -0
  306. package/dist/state/__tests__/session.test.d.ts.map +1 -0
  307. package/dist/state/__tests__/session.test.js +468 -0
  308. package/dist/state/__tests__/session.test.js.map +1 -0
  309. package/dist/state/directory.d.ts +42 -0
  310. package/dist/state/directory.d.ts.map +1 -0
  311. package/dist/state/directory.js +170 -0
  312. package/dist/state/directory.js.map +1 -0
  313. package/dist/state/errors.d.ts +44 -0
  314. package/dist/state/errors.d.ts.map +1 -0
  315. package/dist/state/errors.js +82 -0
  316. package/dist/state/errors.js.map +1 -0
  317. package/dist/state/fleet-state.d.ts +126 -0
  318. package/dist/state/fleet-state.d.ts.map +1 -0
  319. package/dist/state/fleet-state.js +196 -0
  320. package/dist/state/fleet-state.js.map +1 -0
  321. package/dist/state/index.d.ts +21 -0
  322. package/dist/state/index.d.ts.map +1 -0
  323. package/dist/state/index.js +30 -0
  324. package/dist/state/index.js.map +1 -0
  325. package/dist/state/job-metadata.d.ts +151 -0
  326. package/dist/state/job-metadata.d.ts.map +1 -0
  327. package/dist/state/job-metadata.js +287 -0
  328. package/dist/state/job-metadata.js.map +1 -0
  329. package/dist/state/job-output.d.ts +116 -0
  330. package/dist/state/job-output.d.ts.map +1 -0
  331. package/dist/state/job-output.js +218 -0
  332. package/dist/state/job-output.js.map +1 -0
  333. package/dist/state/schemas/__tests__/job-output.test.d.ts +2 -0
  334. package/dist/state/schemas/__tests__/job-output.test.d.ts.map +1 -0
  335. package/dist/state/schemas/__tests__/job-output.test.js +279 -0
  336. package/dist/state/schemas/__tests__/job-output.test.js.map +1 -0
  337. package/dist/state/schemas/fleet-state.d.ts +249 -0
  338. package/dist/state/schemas/fleet-state.d.ts.map +1 -0
  339. package/dist/state/schemas/fleet-state.js +97 -0
  340. package/dist/state/schemas/fleet-state.js.map +1 -0
  341. package/dist/state/schemas/index.d.ts +10 -0
  342. package/dist/state/schemas/index.d.ts.map +1 -0
  343. package/dist/state/schemas/index.js +10 -0
  344. package/dist/state/schemas/index.js.map +1 -0
  345. package/dist/state/schemas/job-metadata.d.ts +118 -0
  346. package/dist/state/schemas/job-metadata.d.ts.map +1 -0
  347. package/dist/state/schemas/job-metadata.js +123 -0
  348. package/dist/state/schemas/job-metadata.js.map +1 -0
  349. package/dist/state/schemas/job-output.d.ts +291 -0
  350. package/dist/state/schemas/job-output.d.ts.map +1 -0
  351. package/dist/state/schemas/job-output.js +132 -0
  352. package/dist/state/schemas/job-output.js.map +1 -0
  353. package/dist/state/schemas/session-info.d.ts +65 -0
  354. package/dist/state/schemas/session-info.d.ts.map +1 -0
  355. package/dist/state/schemas/session-info.js +58 -0
  356. package/dist/state/schemas/session-info.js.map +1 -0
  357. package/dist/state/session.d.ts +92 -0
  358. package/dist/state/session.d.ts.map +1 -0
  359. package/dist/state/session.js +173 -0
  360. package/dist/state/session.js.map +1 -0
  361. package/dist/state/types.d.ts +54 -0
  362. package/dist/state/types.d.ts.map +1 -0
  363. package/dist/state/types.js +18 -0
  364. package/dist/state/types.js.map +1 -0
  365. package/dist/state/utils/__tests__/atomic.test.d.ts +2 -0
  366. package/dist/state/utils/__tests__/atomic.test.d.ts.map +1 -0
  367. package/dist/state/utils/__tests__/atomic.test.js +537 -0
  368. package/dist/state/utils/__tests__/atomic.test.js.map +1 -0
  369. package/dist/state/utils/__tests__/reads.test.d.ts +2 -0
  370. package/dist/state/utils/__tests__/reads.test.d.ts.map +1 -0
  371. package/dist/state/utils/__tests__/reads.test.js +792 -0
  372. package/dist/state/utils/__tests__/reads.test.js.map +1 -0
  373. package/dist/state/utils/atomic.d.ts +89 -0
  374. package/dist/state/utils/atomic.d.ts.map +1 -0
  375. package/dist/state/utils/atomic.js +157 -0
  376. package/dist/state/utils/atomic.js.map +1 -0
  377. package/dist/state/utils/index.d.ts +6 -0
  378. package/dist/state/utils/index.d.ts.map +1 -0
  379. package/dist/state/utils/index.js +6 -0
  380. package/dist/state/utils/index.js.map +1 -0
  381. package/dist/state/utils/reads.d.ts +196 -0
  382. package/dist/state/utils/reads.d.ts.map +1 -0
  383. package/dist/state/utils/reads.js +346 -0
  384. package/dist/state/utils/reads.js.map +1 -0
  385. package/dist/work-sources/__tests__/github.test.d.ts +2 -0
  386. package/dist/work-sources/__tests__/github.test.d.ts.map +1 -0
  387. package/dist/work-sources/__tests__/github.test.js +1334 -0
  388. package/dist/work-sources/__tests__/github.test.js.map +1 -0
  389. package/dist/work-sources/__tests__/manager.test.d.ts +2 -0
  390. package/dist/work-sources/__tests__/manager.test.d.ts.map +1 -0
  391. package/dist/work-sources/__tests__/manager.test.js +424 -0
  392. package/dist/work-sources/__tests__/manager.test.js.map +1 -0
  393. package/dist/work-sources/__tests__/registry.test.d.ts +2 -0
  394. package/dist/work-sources/__tests__/registry.test.d.ts.map +1 -0
  395. package/dist/work-sources/__tests__/registry.test.js +381 -0
  396. package/dist/work-sources/__tests__/registry.test.js.map +1 -0
  397. package/dist/work-sources/__tests__/types.test.d.ts +2 -0
  398. package/dist/work-sources/__tests__/types.test.d.ts.map +1 -0
  399. package/dist/work-sources/__tests__/types.test.js +406 -0
  400. package/dist/work-sources/__tests__/types.test.js.map +1 -0
  401. package/dist/work-sources/adapters/github.d.ts +290 -0
  402. package/dist/work-sources/adapters/github.d.ts.map +1 -0
  403. package/dist/work-sources/adapters/github.js +803 -0
  404. package/dist/work-sources/adapters/github.js.map +1 -0
  405. package/dist/work-sources/adapters/index.d.ts +10 -0
  406. package/dist/work-sources/adapters/index.d.ts.map +1 -0
  407. package/dist/work-sources/adapters/index.js +31 -0
  408. package/dist/work-sources/adapters/index.js.map +1 -0
  409. package/dist/work-sources/errors.d.ts +40 -0
  410. package/dist/work-sources/errors.d.ts.map +1 -0
  411. package/dist/work-sources/errors.js +54 -0
  412. package/dist/work-sources/errors.js.map +1 -0
  413. package/dist/work-sources/index.d.ts +105 -0
  414. package/dist/work-sources/index.d.ts.map +1 -0
  415. package/dist/work-sources/index.js +24 -0
  416. package/dist/work-sources/index.js.map +1 -0
  417. package/dist/work-sources/manager.d.ts +370 -0
  418. package/dist/work-sources/manager.d.ts.map +1 -0
  419. package/dist/work-sources/manager.js +61 -0
  420. package/dist/work-sources/manager.js.map +1 -0
  421. package/dist/work-sources/registry.d.ts +128 -0
  422. package/dist/work-sources/registry.d.ts.map +1 -0
  423. package/dist/work-sources/registry.js +132 -0
  424. package/dist/work-sources/registry.js.map +1 -0
  425. package/dist/work-sources/types.d.ts +127 -0
  426. package/dist/work-sources/types.d.ts.map +1 -0
  427. package/dist/work-sources/types.js +8 -0
  428. package/dist/work-sources/types.js.map +1 -0
  429. package/package.json +23 -0
  430. package/src/config/__tests__/agent.test.ts +864 -0
  431. package/src/config/__tests__/interpolate.test.ts +644 -0
  432. package/src/config/__tests__/loader.test.ts +784 -0
  433. package/src/config/__tests__/merge.test.ts +751 -0
  434. package/src/config/__tests__/parser.test.ts +533 -0
  435. package/src/config/__tests__/schema.test.ts +873 -0
  436. package/src/config/index.ts +119 -0
  437. package/src/config/interpolate.ts +189 -0
  438. package/src/config/loader.ts +472 -0
  439. package/src/config/merge.ts +246 -0
  440. package/src/config/parser.ts +376 -0
  441. package/src/config/schema.ts +346 -0
  442. package/src/fleet-manager/__tests__/coverage.test.ts +2869 -0
  443. package/src/fleet-manager/__tests__/errors.test.ts +660 -0
  444. package/src/fleet-manager/__tests__/event-helpers.test.ts +448 -0
  445. package/src/fleet-manager/__tests__/integration.test.ts +1209 -0
  446. package/src/fleet-manager/__tests__/job-control.test.ts +283 -0
  447. package/src/fleet-manager/__tests__/job-manager.test.ts +869 -0
  448. package/src/fleet-manager/__tests__/job-queue.test.ts +401 -0
  449. package/src/fleet-manager/__tests__/reload.test.ts +751 -0
  450. package/src/fleet-manager/__tests__/status-queries.test.ts +595 -0
  451. package/src/fleet-manager/__tests__/trigger.test.ts +601 -0
  452. package/src/fleet-manager/errors.ts +747 -0
  453. package/src/fleet-manager/event-types.ts +378 -0
  454. package/src/fleet-manager/fleet-manager.ts +2315 -0
  455. package/src/fleet-manager/index.ts +128 -0
  456. package/src/fleet-manager/job-manager.ts +663 -0
  457. package/src/fleet-manager/job-queue.ts +798 -0
  458. package/src/fleet-manager/types.ts +839 -0
  459. package/src/index.ts +32 -0
  460. package/src/runner/__tests__/errors.test.ts +382 -0
  461. package/src/runner/__tests__/job-executor.test.ts +1708 -0
  462. package/src/runner/__tests__/message-processor.test.ts +960 -0
  463. package/src/runner/__tests__/sdk-adapter.test.ts +626 -0
  464. package/src/runner/errors.ts +307 -0
  465. package/src/runner/index.ts +57 -0
  466. package/src/runner/job-executor.ts +448 -0
  467. package/src/runner/message-processor.ts +355 -0
  468. package/src/runner/sdk-adapter.ts +191 -0
  469. package/src/runner/types.ts +158 -0
  470. package/src/scheduler/__tests__/errors.test.ts +159 -0
  471. package/src/scheduler/__tests__/interval.test.ts +515 -0
  472. package/src/scheduler/__tests__/schedule-runner.test.ts +798 -0
  473. package/src/scheduler/__tests__/schedule-state.test.ts +671 -0
  474. package/src/scheduler/__tests__/scheduler.test.ts +1280 -0
  475. package/src/scheduler/errors.ts +101 -0
  476. package/src/scheduler/index.ts +53 -0
  477. package/src/scheduler/interval.ts +189 -0
  478. package/src/scheduler/schedule-runner.ts +442 -0
  479. package/src/scheduler/schedule-state.ts +211 -0
  480. package/src/scheduler/scheduler.ts +570 -0
  481. package/src/scheduler/types.ts +216 -0
  482. package/src/state/__tests__/directory.test.ts +595 -0
  483. package/src/state/__tests__/fleet-state.test.ts +868 -0
  484. package/src/state/__tests__/job-metadata-schema.test.ts +414 -0
  485. package/src/state/__tests__/job-metadata.test.ts +831 -0
  486. package/src/state/__tests__/job-output.test.ts +856 -0
  487. package/src/state/__tests__/session-schema.test.ts +378 -0
  488. package/src/state/__tests__/session.test.ts +604 -0
  489. package/src/state/directory.ts +217 -0
  490. package/src/state/errors.ts +97 -0
  491. package/src/state/fleet-state.ts +284 -0
  492. package/src/state/index.ts +79 -0
  493. package/src/state/job-metadata.ts +445 -0
  494. package/src/state/job-output.ts +316 -0
  495. package/src/state/schemas/__tests__/job-output.test.ts +338 -0
  496. package/src/state/schemas/fleet-state.ts +120 -0
  497. package/src/state/schemas/index.ts +67 -0
  498. package/src/state/schemas/job-metadata.ts +181 -0
  499. package/src/state/schemas/job-output.ts +177 -0
  500. package/src/state/schemas/session-info.ts +92 -0
  501. package/src/state/session.ts +253 -0
  502. package/src/state/types.ts +59 -0
  503. package/src/state/utils/__tests__/atomic.test.ts +723 -0
  504. package/src/state/utils/__tests__/reads.test.ts +1071 -0
  505. package/src/state/utils/atomic.ts +221 -0
  506. package/src/state/utils/index.ts +6 -0
  507. package/src/state/utils/reads.ts +512 -0
  508. package/src/work-sources/__tests__/github.test.ts +1800 -0
  509. package/src/work-sources/__tests__/manager.test.ts +529 -0
  510. package/src/work-sources/__tests__/registry.test.ts +477 -0
  511. package/src/work-sources/__tests__/types.test.ts +479 -0
  512. package/src/work-sources/adapters/github.ts +1166 -0
  513. package/src/work-sources/adapters/index.ts +64 -0
  514. package/src/work-sources/errors.ts +71 -0
  515. package/src/work-sources/index.ts +148 -0
  516. package/src/work-sources/manager.ts +413 -0
  517. package/src/work-sources/registry.ts +178 -0
  518. package/src/work-sources/types.ts +161 -0
  519. package/tsconfig.json +9 -0
  520. package/vitest.config.ts +19 -0
@@ -0,0 +1,512 @@
1
+ /**
2
+ * Safe concurrent read utilities
3
+ *
4
+ * Provides read operations that handle concurrent access safely.
5
+ * Read operations don't require locks - multiple readers can access
6
+ * files simultaneously. These utilities handle edge cases like:
7
+ * - Files being written to during read (retry on partial read)
8
+ * - Empty or truncated YAML files
9
+ * - Incomplete last lines in JSONL files
10
+ */
11
+
12
+ import { readFile } from "node:fs/promises";
13
+ import { parse as parseYaml, YAMLParseError } from "yaml";
14
+
15
+ /**
16
+ * Error thrown when a safe read operation fails
17
+ */
18
+ export class SafeReadError extends Error {
19
+ public readonly path: string;
20
+ public readonly code?: string;
21
+
22
+ constructor(message: string, path: string, cause?: Error) {
23
+ super(message);
24
+ this.name = "SafeReadError";
25
+ this.path = path;
26
+ this.cause = cause;
27
+ this.code = (cause as NodeJS.ErrnoException | undefined)?.code;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Options for safe read operations
33
+ */
34
+ export interface SafeReadOptions {
35
+ /**
36
+ * Maximum number of retry attempts for transient failures.
37
+ * Default: 3
38
+ */
39
+ maxRetries?: number;
40
+
41
+ /**
42
+ * Base delay in milliseconds between retries.
43
+ * Uses exponential backoff: delay = baseDelayMs * 2^attempt
44
+ * Default: 10
45
+ */
46
+ baseDelayMs?: number;
47
+
48
+ /**
49
+ * Injectable read function for testing
50
+ * @internal
51
+ */
52
+ readFn?: (path: string, encoding: BufferEncoding) => Promise<string>;
53
+ }
54
+
55
+ /**
56
+ * Options for safeReadJsonl
57
+ */
58
+ export interface SafeReadJsonlOptions extends SafeReadOptions {
59
+ /**
60
+ * Whether to skip invalid JSON lines instead of failing.
61
+ * Default: false - only skips truly incomplete last line
62
+ */
63
+ skipInvalidLines?: boolean;
64
+ }
65
+
66
+ /**
67
+ * Result type for safe YAML read operations
68
+ */
69
+ export type SafeReadYamlResult<T> =
70
+ | { success: true; data: T }
71
+ | { success: false; error: SafeReadError };
72
+
73
+ /**
74
+ * Result type for safe JSONL read operations
75
+ */
76
+ export type SafeReadJsonlResult<T> =
77
+ | { success: true; data: T[]; skippedLines: number }
78
+ | { success: false; error: SafeReadError };
79
+
80
+ /**
81
+ * Check if an error is likely a transient read error that should be retried.
82
+ * This happens when reading a file while it's being written atomically.
83
+ *
84
+ * We treat YAML parse errors as potentially transient because:
85
+ * 1. If a read occurs during an atomic write, the file might be empty or partially written
86
+ * 2. The retry gives time for the atomic rename to complete
87
+ * 3. Non-transient errors (like truly malformed YAML) will fail consistently
88
+ */
89
+ function isTransientReadError(error: unknown): boolean {
90
+ if (!(error instanceof Error)) return false;
91
+
92
+ // All YAML parse errors are potentially transient - they could indicate
93
+ // a partial read during an atomic write operation
94
+ if (error instanceof YAMLParseError) {
95
+ return true;
96
+ }
97
+
98
+ // JSON parse errors for incomplete content
99
+ if (error instanceof SyntaxError) {
100
+ const msg = error.message.toLowerCase();
101
+ if (
102
+ msg.includes("unexpected end") ||
103
+ msg.includes("unexpected token") ||
104
+ msg.includes("unterminated")
105
+ ) {
106
+ return true;
107
+ }
108
+ }
109
+
110
+ return false;
111
+ }
112
+
113
+ /**
114
+ * Wait for a delay using exponential backoff
115
+ */
116
+ async function backoffDelay(attempt: number, baseDelayMs: number): Promise<void> {
117
+ const delay = baseDelayMs * Math.pow(2, attempt);
118
+ await new Promise((resolve) => setTimeout(resolve, delay));
119
+ }
120
+
121
+ /**
122
+ * Read and parse a YAML file safely with retry logic.
123
+ *
124
+ * This function handles:
125
+ * - Files being written to during read (retries on parse failure)
126
+ * - Empty files (returns null/undefined based on YAML spec)
127
+ * - Truncated files (retries then fails gracefully)
128
+ *
129
+ * Read operations don't require locks - multiple concurrent reads are safe.
130
+ * The retry logic handles the case where a read occurs during an atomic write.
131
+ *
132
+ * @param filePath - Path to the YAML file
133
+ * @param options - Read options including retry configuration
134
+ * @returns Promise resolving to SafeReadYamlResult with success/failure
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * const result = await safeReadYaml<MyConfig>('/path/to/config.yaml');
139
+ * if (result.success) {
140
+ * console.log(result.data);
141
+ * } else {
142
+ * console.error(result.error.message);
143
+ * }
144
+ * ```
145
+ */
146
+ export async function safeReadYaml<T = unknown>(
147
+ filePath: string,
148
+ options: SafeReadOptions = {}
149
+ ): Promise<SafeReadYamlResult<T>> {
150
+ const {
151
+ maxRetries = 3,
152
+ baseDelayMs = 10,
153
+ readFn = (path, encoding) => readFile(path, encoding),
154
+ } = options;
155
+
156
+ let lastError: Error | undefined;
157
+
158
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
159
+ try {
160
+ const content = await readFn(filePath, "utf-8");
161
+
162
+ // Handle empty file - YAML spec says empty doc is null
163
+ if (content.trim() === "") {
164
+ return { success: true, data: null as T };
165
+ }
166
+
167
+ const parsed = parseYaml(content) as T;
168
+ return { success: true, data: parsed };
169
+ } catch (error) {
170
+ lastError = error as Error;
171
+
172
+ // File not found or permission errors are not transient
173
+ const code = (error as NodeJS.ErrnoException).code;
174
+ if (code === "ENOENT" || code === "EACCES" || code === "EPERM") {
175
+ return {
176
+ success: false,
177
+ error: new SafeReadError(
178
+ `Failed to read YAML file ${filePath}: ${(error as Error).message}`,
179
+ filePath,
180
+ error as Error
181
+ ),
182
+ };
183
+ }
184
+
185
+ // Check if this is a transient error worth retrying
186
+ if (isTransientReadError(error) && attempt < maxRetries) {
187
+ await backoffDelay(attempt, baseDelayMs);
188
+ continue;
189
+ }
190
+
191
+ // Non-transient error or retries exhausted
192
+ return {
193
+ success: false,
194
+ error: new SafeReadError(
195
+ `Failed to parse YAML file ${filePath}: ${(error as Error).message}`,
196
+ filePath,
197
+ error as Error
198
+ ),
199
+ };
200
+ }
201
+ }
202
+
203
+ // Should not reach here, but handle it just in case
204
+ return {
205
+ success: false,
206
+ error: new SafeReadError(
207
+ `Failed to read YAML file ${filePath} after ${maxRetries + 1} attempts`,
208
+ filePath,
209
+ lastError
210
+ ),
211
+ };
212
+ }
213
+
214
+ /**
215
+ * Read and parse a JSONL file safely, handling incomplete last lines.
216
+ *
217
+ * This function handles:
218
+ * - Incomplete last line (truncates to last valid line)
219
+ * - Empty files (returns empty array)
220
+ * - Files being written to during read (retries on failure)
221
+ *
222
+ * JSONL (JSON Lines) format has one JSON object per line. When reading
223
+ * a file that's being appended to, the last line may be incomplete.
224
+ * This function safely truncates to the last complete line.
225
+ *
226
+ * Read operations don't require locks - multiple concurrent reads are safe.
227
+ *
228
+ * @param filePath - Path to the JSONL file
229
+ * @param options - Read options including retry configuration
230
+ * @returns Promise resolving to SafeReadJsonlResult with array of parsed objects
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * const result = await safeReadJsonl<LogEntry>('/path/to/events.jsonl');
235
+ * if (result.success) {
236
+ * console.log(`Read ${result.data.length} entries, skipped ${result.skippedLines}`);
237
+ * }
238
+ * ```
239
+ */
240
+ export async function safeReadJsonl<T = unknown>(
241
+ filePath: string,
242
+ options: SafeReadJsonlOptions = {}
243
+ ): Promise<SafeReadJsonlResult<T>> {
244
+ const {
245
+ maxRetries = 3,
246
+ baseDelayMs = 10,
247
+ skipInvalidLines = false,
248
+ readFn = (path, encoding) => readFile(path, encoding),
249
+ } = options;
250
+
251
+ let lastError: Error | undefined;
252
+
253
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
254
+ try {
255
+ const content = await readFn(filePath, "utf-8");
256
+
257
+ // Handle empty file
258
+ if (content.trim() === "") {
259
+ return { success: true, data: [], skippedLines: 0 };
260
+ }
261
+
262
+ const result = parseJsonlContent<T>(content, skipInvalidLines);
263
+ return { success: true, ...result };
264
+ } catch (error) {
265
+ lastError = error as Error;
266
+
267
+ // File not found or permission errors are not transient
268
+ const code = (error as NodeJS.ErrnoException).code;
269
+ if (code === "ENOENT" || code === "EACCES" || code === "EPERM") {
270
+ return {
271
+ success: false,
272
+ error: new SafeReadError(
273
+ `Failed to read JSONL file ${filePath}: ${(error as Error).message}`,
274
+ filePath,
275
+ error as Error
276
+ ),
277
+ };
278
+ }
279
+
280
+ // Retry on transient errors
281
+ if (attempt < maxRetries) {
282
+ await backoffDelay(attempt, baseDelayMs);
283
+ continue;
284
+ }
285
+ }
286
+ }
287
+
288
+ return {
289
+ success: false,
290
+ error: new SafeReadError(
291
+ `Failed to read JSONL file ${filePath} after ${maxRetries + 1} attempts`,
292
+ filePath,
293
+ lastError
294
+ ),
295
+ };
296
+ }
297
+
298
+ /**
299
+ * Parse JSONL content, handling incomplete last line.
300
+ *
301
+ * @internal
302
+ */
303
+ function parseJsonlContent<T>(
304
+ content: string,
305
+ skipInvalidLines: boolean
306
+ ): { data: T[]; skippedLines: number } {
307
+ const lines = content.split("\n");
308
+ const result: T[] = [];
309
+ let skippedLines = 0;
310
+
311
+ for (let i = 0; i < lines.length; i++) {
312
+ const line = lines[i].trim();
313
+
314
+ // Skip empty lines
315
+ if (line === "") {
316
+ continue;
317
+ }
318
+
319
+ try {
320
+ const parsed = JSON.parse(line) as T;
321
+ result.push(parsed);
322
+ } catch (error) {
323
+ // Last line may be incomplete - always skip it silently
324
+ if (i === lines.length - 1 || i === lines.length - 2) {
325
+ // Could be the actual last line or second-to-last if file ends with \n
326
+ skippedLines++;
327
+ continue;
328
+ }
329
+
330
+ // For middle lines, either skip or fail based on option
331
+ if (skipInvalidLines) {
332
+ skippedLines++;
333
+ continue;
334
+ }
335
+
336
+ // Re-throw for non-last-line errors when not skipping
337
+ throw new SafeReadError(
338
+ `Invalid JSON on line ${i + 1}: ${(error as Error).message}`,
339
+ "",
340
+ error as Error
341
+ );
342
+ }
343
+ }
344
+
345
+ return { data: result, skippedLines };
346
+ }
347
+
348
+ /**
349
+ * Read a YAML file with retry logic, throwing on failure.
350
+ *
351
+ * This is a convenience wrapper around safeReadYaml that throws
352
+ * instead of returning a result object.
353
+ *
354
+ * @param filePath - Path to the YAML file
355
+ * @param options - Read options
356
+ * @returns Promise resolving to parsed YAML content
357
+ * @throws SafeReadError on failure
358
+ */
359
+ export async function readYaml<T = unknown>(
360
+ filePath: string,
361
+ options: SafeReadOptions = {}
362
+ ): Promise<T> {
363
+ const result = await safeReadYaml<T>(filePath, options);
364
+ if (!result.success) {
365
+ throw result.error;
366
+ }
367
+ return result.data;
368
+ }
369
+
370
+ /**
371
+ * Read a JSONL file, handling incomplete last line, throwing on failure.
372
+ *
373
+ * This is a convenience wrapper around safeReadJsonl that throws
374
+ * instead of returning a result object.
375
+ *
376
+ * @param filePath - Path to the JSONL file
377
+ * @param options - Read options
378
+ * @returns Promise resolving to array of parsed objects
379
+ * @throws SafeReadError on failure
380
+ */
381
+ export async function readJsonl<T = unknown>(
382
+ filePath: string,
383
+ options: SafeReadJsonlOptions = {}
384
+ ): Promise<T[]> {
385
+ const result = await safeReadJsonl<T>(filePath, options);
386
+ if (!result.success) {
387
+ throw result.error;
388
+ }
389
+ return result.data;
390
+ }
391
+
392
+ /**
393
+ * Result type for safe JSON read operations
394
+ */
395
+ export type SafeReadJsonResult<T> =
396
+ | { success: true; data: T }
397
+ | { success: false; error: SafeReadError };
398
+
399
+ /**
400
+ * Read and parse a JSON file safely with retry logic.
401
+ *
402
+ * This function handles:
403
+ * - Files being written to during read (retries on parse failure)
404
+ * - Empty files (returns null)
405
+ * - Truncated files (retries then fails gracefully)
406
+ *
407
+ * Read operations don't require locks - multiple concurrent reads are safe.
408
+ * The retry logic handles the case where a read occurs during an atomic write.
409
+ *
410
+ * @param filePath - Path to the JSON file
411
+ * @param options - Read options including retry configuration
412
+ * @returns Promise resolving to SafeReadJsonResult with success/failure
413
+ *
414
+ * @example
415
+ * ```typescript
416
+ * const result = await safeReadJson<MyConfig>('/path/to/config.json');
417
+ * if (result.success) {
418
+ * console.log(result.data);
419
+ * } else {
420
+ * console.error(result.error.message);
421
+ * }
422
+ * ```
423
+ */
424
+ export async function safeReadJson<T = unknown>(
425
+ filePath: string,
426
+ options: SafeReadOptions = {}
427
+ ): Promise<SafeReadJsonResult<T>> {
428
+ const {
429
+ maxRetries = 3,
430
+ baseDelayMs = 10,
431
+ readFn = (path, encoding) => readFile(path, encoding),
432
+ } = options;
433
+
434
+ let lastError: Error | undefined;
435
+
436
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
437
+ try {
438
+ const content = await readFn(filePath, "utf-8");
439
+
440
+ // Handle empty file
441
+ if (content.trim() === "") {
442
+ return { success: true, data: null as T };
443
+ }
444
+
445
+ const parsed = JSON.parse(content) as T;
446
+ return { success: true, data: parsed };
447
+ } catch (error) {
448
+ lastError = error as Error;
449
+
450
+ // File not found or permission errors are not transient
451
+ const code = (error as NodeJS.ErrnoException).code;
452
+ if (code === "ENOENT" || code === "EACCES" || code === "EPERM") {
453
+ return {
454
+ success: false,
455
+ error: new SafeReadError(
456
+ `Failed to read JSON file ${filePath}: ${(error as Error).message}`,
457
+ filePath,
458
+ error as Error
459
+ ),
460
+ };
461
+ }
462
+
463
+ // Check if this is a transient error worth retrying
464
+ if (isTransientReadError(error) && attempt < maxRetries) {
465
+ await backoffDelay(attempt, baseDelayMs);
466
+ continue;
467
+ }
468
+
469
+ // Non-transient error or retries exhausted
470
+ return {
471
+ success: false,
472
+ error: new SafeReadError(
473
+ `Failed to parse JSON file ${filePath}: ${(error as Error).message}`,
474
+ filePath,
475
+ error as Error
476
+ ),
477
+ };
478
+ }
479
+ }
480
+
481
+ // Should not reach here, but handle it just in case
482
+ return {
483
+ success: false,
484
+ error: new SafeReadError(
485
+ `Failed to read JSON file ${filePath} after ${maxRetries + 1} attempts`,
486
+ filePath,
487
+ lastError
488
+ ),
489
+ };
490
+ }
491
+
492
+ /**
493
+ * Read a JSON file with retry logic, throwing on failure.
494
+ *
495
+ * This is a convenience wrapper around safeReadJson that throws
496
+ * instead of returning a result object.
497
+ *
498
+ * @param filePath - Path to the JSON file
499
+ * @param options - Read options
500
+ * @returns Promise resolving to parsed JSON content
501
+ * @throws SafeReadError on failure
502
+ */
503
+ export async function readJson<T = unknown>(
504
+ filePath: string,
505
+ options: SafeReadOptions = {}
506
+ ): Promise<T> {
507
+ const result = await safeReadJson<T>(filePath, options);
508
+ if (!result.success) {
509
+ throw result.error;
510
+ }
511
+ return result.data;
512
+ }