pixivflow 2.18.0 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (510) hide show
  1. package/dist/commands/SchedulerCommand.js +105 -45
  2. package/dist/commands/SchedulerIdleLifecycle.d.ts +82 -0
  3. package/dist/commands/SchedulerIdleLifecycle.js +135 -0
  4. package/dist/commands/scheduler-runtime.d.ts +6 -0
  5. package/dist/commands/scheduler-runtime.js +77 -9
  6. package/dist/config/types.d.ts +33 -0
  7. package/dist/config/validation.js +10 -1
  8. package/dist/download/DownloadManager.d.ts +9 -0
  9. package/dist/download/DownloadManager.js +18 -1
  10. package/dist/package.json +1 -1
  11. package/dist/pixiv-client/PixivFlowPixivClient.d.ts +7 -1
  12. package/dist/pixiv-client/PixivFlowPixivClient.js +14 -8
  13. package/dist/pixiv-client/TargetSearchRunner.d.ts +2 -2
  14. package/dist/pixiv-client/TargetSearchRunner.js +21 -14
  15. package/dist/scheduler/MultiScheduleManager.d.ts +33 -1
  16. package/dist/scheduler/MultiScheduleManager.js +115 -2
  17. package/dist/scheduler/ScheduleTriggerServer.d.ts +32 -9
  18. package/dist/scheduler/ScheduleTriggerServer.js +34 -6
  19. package/dist/scheduler/Scheduler.d.ts +28 -1
  20. package/dist/scheduler/Scheduler.js +82 -7
  21. package/dist/scheduler/SlotCoordinator.d.ts +28 -3
  22. package/dist/scheduler/SlotCoordinator.js +31 -5
  23. package/dist/storage/repositories/SlotRepository.d.ts +28 -0
  24. package/dist/storage/repositories/SlotRepository.js +42 -4
  25. package/dist/topic/TopicPipeline.d.ts +15 -1
  26. package/dist/topic/TopicPipeline.js +18 -2
  27. package/dist/topic/TopicResolver.d.ts +5 -1
  28. package/dist/topic/TopicResolver.js +32 -6
  29. package/dist/topic/createTopicPipeline.d.ts +2 -2
  30. package/dist/topic/createTopicPipeline.js +5 -5
  31. package/dist/topic/types.d.ts +5 -1
  32. package/dist/utils/errors.d.ts +18 -0
  33. package/dist/utils/errors.js +30 -0
  34. package/dist/version.js +1 -1
  35. package/dist/webui/package.json +1 -1
  36. package/package.json +2 -1
  37. package/dist/auth/PixivAuth.d.ts.map +0 -1
  38. package/dist/auth/PixivAuth.js.map +0 -1
  39. package/dist/batch/executionResult.d.ts.map +0 -1
  40. package/dist/batch/executionResult.js.map +0 -1
  41. package/dist/batch/runDiagnostics.d.ts.map +0 -1
  42. package/dist/batch/runDiagnostics.js.map +0 -1
  43. package/dist/cli/ArgumentParser.d.ts.map +0 -1
  44. package/dist/cli/ArgumentParser.js.map +0 -1
  45. package/dist/cli/InteractivePrompt.d.ts.map +0 -1
  46. package/dist/cli/InteractivePrompt.js.map +0 -1
  47. package/dist/commands/BackupCommand.d.ts.map +0 -1
  48. package/dist/commands/BackupCommand.js.map +0 -1
  49. package/dist/commands/Command.d.ts.map +0 -1
  50. package/dist/commands/Command.js.map +0 -1
  51. package/dist/commands/CommandRegistry.d.ts.map +0 -1
  52. package/dist/commands/CommandRegistry.js.map +0 -1
  53. package/dist/commands/ConfigCommand.d.ts.map +0 -1
  54. package/dist/commands/ConfigCommand.js.map +0 -1
  55. package/dist/commands/DiagnoseEgressCommand.d.ts.map +0 -1
  56. package/dist/commands/DiagnoseEgressCommand.js.map +0 -1
  57. package/dist/commands/DirsCommand.d.ts.map +0 -1
  58. package/dist/commands/DirsCommand.js.map +0 -1
  59. package/dist/commands/DoctorCommand.d.ts.map +0 -1
  60. package/dist/commands/DoctorCommand.js.map +0 -1
  61. package/dist/commands/DownloadCommand.d.ts.map +0 -1
  62. package/dist/commands/DownloadCommand.js.map +0 -1
  63. package/dist/commands/ExecuteSlotCommand.d.ts.map +0 -1
  64. package/dist/commands/ExecuteSlotCommand.js.map +0 -1
  65. package/dist/commands/HealthCommand.d.ts.map +0 -1
  66. package/dist/commands/HealthCommand.js.map +0 -1
  67. package/dist/commands/HelpCommand.d.ts.map +0 -1
  68. package/dist/commands/HelpCommand.js.map +0 -1
  69. package/dist/commands/LoginCommand.d.ts.map +0 -1
  70. package/dist/commands/LoginCommand.js.map +0 -1
  71. package/dist/commands/LoginHeadlessCommand.d.ts.map +0 -1
  72. package/dist/commands/LoginHeadlessCommand.js.map +0 -1
  73. package/dist/commands/LogsCommand.d.ts.map +0 -1
  74. package/dist/commands/LogsCommand.js.map +0 -1
  75. package/dist/commands/MaintainCommand.d.ts.map +0 -1
  76. package/dist/commands/MaintainCommand.js.map +0 -1
  77. package/dist/commands/MigrateConfigCommand.d.ts.map +0 -1
  78. package/dist/commands/MigrateConfigCommand.js.map +0 -1
  79. package/dist/commands/MonitorCommand.d.ts.map +0 -1
  80. package/dist/commands/MonitorCommand.js.map +0 -1
  81. package/dist/commands/NormalizeCommand.d.ts.map +0 -1
  82. package/dist/commands/NormalizeCommand.js.map +0 -1
  83. package/dist/commands/OutboxCommand.d.ts.map +0 -1
  84. package/dist/commands/OutboxCommand.js.map +0 -1
  85. package/dist/commands/RandomDownloadCommand.d.ts.map +0 -1
  86. package/dist/commands/RandomDownloadCommand.js.map +0 -1
  87. package/dist/commands/ReconcileCommand.d.ts.map +0 -1
  88. package/dist/commands/ReconcileCommand.js.map +0 -1
  89. package/dist/commands/RefreshCommand.d.ts.map +0 -1
  90. package/dist/commands/RefreshCommand.js.map +0 -1
  91. package/dist/commands/RunsCommand.d.ts.map +0 -1
  92. package/dist/commands/RunsCommand.js.map +0 -1
  93. package/dist/commands/SchedulerCommand.d.ts.map +0 -1
  94. package/dist/commands/SchedulerCommand.js.map +0 -1
  95. package/dist/commands/SchedulerRunOnceCommand.d.ts.map +0 -1
  96. package/dist/commands/SchedulerRunOnceCommand.js.map +0 -1
  97. package/dist/commands/SetupCommand.d.ts.map +0 -1
  98. package/dist/commands/SetupCommand.js.map +0 -1
  99. package/dist/commands/StatusCommand.d.ts.map +0 -1
  100. package/dist/commands/StatusCommand.js.map +0 -1
  101. package/dist/commands/TagsCommand.d.ts.map +0 -1
  102. package/dist/commands/TagsCommand.js.map +0 -1
  103. package/dist/commands/TopicCommand.d.ts.map +0 -1
  104. package/dist/commands/TopicCommand.js.map +0 -1
  105. package/dist/commands/VersionCommand.d.ts.map +0 -1
  106. package/dist/commands/VersionCommand.js.map +0 -1
  107. package/dist/commands/WebUICommand.d.ts.map +0 -1
  108. package/dist/commands/WebUICommand.js.map +0 -1
  109. package/dist/commands/index.d.ts.map +0 -1
  110. package/dist/commands/index.js.map +0 -1
  111. package/dist/commands/metadata.d.ts.map +0 -1
  112. package/dist/commands/metadata.js.map +0 -1
  113. package/dist/commands/scheduler-runtime.d.ts.map +0 -1
  114. package/dist/commands/scheduler-runtime.js.map +0 -1
  115. package/dist/commands/suggestions.d.ts.map +0 -1
  116. package/dist/commands/suggestions.js.map +0 -1
  117. package/dist/commands/types.d.ts.map +0 -1
  118. package/dist/commands/types.js.map +0 -1
  119. package/dist/config/defaults.d.ts.map +0 -1
  120. package/dist/config/defaults.js.map +0 -1
  121. package/dist/config/environment.d.ts.map +0 -1
  122. package/dist/config/environment.js.map +0 -1
  123. package/dist/config/loader.d.ts.map +0 -1
  124. package/dist/config/loader.js.map +0 -1
  125. package/dist/config/path-resolution.d.ts.map +0 -1
  126. package/dist/config/path-resolution.js.map +0 -1
  127. package/dist/config/placeholders.d.ts.map +0 -1
  128. package/dist/config/placeholders.js.map +0 -1
  129. package/dist/config/types.d.ts.map +0 -1
  130. package/dist/config/types.js.map +0 -1
  131. package/dist/config/validation.d.ts.map +0 -1
  132. package/dist/config/validation.js.map +0 -1
  133. package/dist/config.d.ts.map +0 -1
  134. package/dist/config.js.map +0 -1
  135. package/dist/delivery/DeliveryAck.d.ts.map +0 -1
  136. package/dist/delivery/DeliveryAck.js.map +0 -1
  137. package/dist/delivery/DeliveryDispatcher.d.ts.map +0 -1
  138. package/dist/delivery/DeliveryDispatcher.js.map +0 -1
  139. package/dist/delivery/DeliveryService.d.ts.map +0 -1
  140. package/dist/delivery/DeliveryService.js.map +0 -1
  141. package/dist/delivery/HttpMultipartDelivery.d.ts.map +0 -1
  142. package/dist/delivery/HttpMultipartDelivery.js.map +0 -1
  143. package/dist/delivery/LegacyOutboxMigration.d.ts.map +0 -1
  144. package/dist/delivery/LegacyOutboxMigration.js.map +0 -1
  145. package/dist/delivery/OutboxWorker.d.ts.map +0 -1
  146. package/dist/delivery/OutboxWorker.js.map +0 -1
  147. package/dist/delivery/TelegramReviewDelivery.d.ts.map +0 -1
  148. package/dist/delivery/TelegramReviewDelivery.js.map +0 -1
  149. package/dist/delivery/errorClass.d.ts.map +0 -1
  150. package/dist/delivery/errorClass.js.map +0 -1
  151. package/dist/delivery/types.d.ts.map +0 -1
  152. package/dist/delivery/types.js.map +0 -1
  153. package/dist/di/Container.d.ts.map +0 -1
  154. package/dist/di/Container.js.map +0 -1
  155. package/dist/di/index.d.ts.map +0 -1
  156. package/dist/di/index.js.map +0 -1
  157. package/dist/download/DownloadManager.d.ts.map +0 -1
  158. package/dist/download/DownloadManager.js.map +0 -1
  159. package/dist/download/FileNormalizationService.d.ts.map +0 -1
  160. package/dist/download/FileNormalizationService.js.map +0 -1
  161. package/dist/download/FileService.d.ts.map +0 -1
  162. package/dist/download/FileService.js.map +0 -1
  163. package/dist/download/IllustrationDownloader.d.ts.map +0 -1
  164. package/dist/download/IllustrationDownloader.js.map +0 -1
  165. package/dist/download/NovelDownloader.d.ts.map +0 -1
  166. package/dist/download/NovelDownloader.js.map +0 -1
  167. package/dist/download/RankingService.d.ts.map +0 -1
  168. package/dist/download/RankingService.js.map +0 -1
  169. package/dist/download/UgoiraConverter.d.ts.map +0 -1
  170. package/dist/download/UgoiraConverter.js.map +0 -1
  171. package/dist/download/exec/DownloadExecutor.d.ts.map +0 -1
  172. package/dist/download/exec/DownloadExecutor.js.map +0 -1
  173. package/dist/download/handlers/IllustrationTargetHandler.d.ts.map +0 -1
  174. package/dist/download/handlers/IllustrationTargetHandler.js.map +0 -1
  175. package/dist/download/handlers/NovelTargetHandler.d.ts.map +0 -1
  176. package/dist/download/handlers/NovelTargetHandler.js.map +0 -1
  177. package/dist/download/normalization/FileNameNormalizer.d.ts.map +0 -1
  178. package/dist/download/normalization/FileNameNormalizer.js.map +0 -1
  179. package/dist/download/normalization/FileOperationUtils.d.ts.map +0 -1
  180. package/dist/download/normalization/FileOperationUtils.js.map +0 -1
  181. package/dist/download/normalization/MetadataLoader.d.ts.map +0 -1
  182. package/dist/download/normalization/MetadataLoader.js.map +0 -1
  183. package/dist/download/pipeline/DownloadPipeline.d.ts.map +0 -1
  184. package/dist/download/pipeline/DownloadPipeline.js.map +0 -1
  185. package/dist/download/plan/DownloadPlanner.d.ts.map +0 -1
  186. package/dist/download/plan/DownloadPlanner.js.map +0 -1
  187. package/dist/download/recovery/ErrorRecovery.d.ts.map +0 -1
  188. package/dist/download/recovery/ErrorRecovery.js.map +0 -1
  189. package/dist/download/report/ProgressReporter.d.ts.map +0 -1
  190. package/dist/download/report/ProgressReporter.js.map +0 -1
  191. package/dist/download-specific.d.ts.map +0 -1
  192. package/dist/download-specific.js.map +0 -1
  193. package/dist/index.d.ts.map +0 -1
  194. package/dist/index.js.map +0 -1
  195. package/dist/interfaces/IDatabase.d.ts.map +0 -1
  196. package/dist/interfaces/IDatabase.js.map +0 -1
  197. package/dist/interfaces/IDownloadManager.d.ts.map +0 -1
  198. package/dist/interfaces/IDownloadManager.js.map +0 -1
  199. package/dist/interfaces/IFileService.d.ts.map +0 -1
  200. package/dist/interfaces/IFileService.js.map +0 -1
  201. package/dist/interfaces/IPixivAuth.d.ts.map +0 -1
  202. package/dist/interfaces/IPixivAuth.js.map +0 -1
  203. package/dist/interfaces/IPixivClient.d.ts.map +0 -1
  204. package/dist/interfaces/IPixivClient.js.map +0 -1
  205. package/dist/interfaces/index.d.ts.map +0 -1
  206. package/dist/interfaces/index.js.map +0 -1
  207. package/dist/logger.d.ts.map +0 -1
  208. package/dist/logger.js.map +0 -1
  209. package/dist/notification/NotificationPolicy.d.ts.map +0 -1
  210. package/dist/notification/NotificationPolicy.js.map +0 -1
  211. package/dist/pixiv/AuthClient.d.ts.map +0 -1
  212. package/dist/pixiv/AuthClient.js.map +0 -1
  213. package/dist/pixiv/PixivClient.d.ts.map +0 -1
  214. package/dist/pixiv/PixivClient.js.map +0 -1
  215. package/dist/pixiv-client/PixivAuthTokenProvider.d.ts.map +0 -1
  216. package/dist/pixiv-client/PixivAuthTokenProvider.js.map +0 -1
  217. package/dist/pixiv-client/PixivFlowPixivClient.d.ts.map +0 -1
  218. package/dist/pixiv-client/PixivFlowPixivClient.js.map +0 -1
  219. package/dist/pixiv-client/TargetSearchRunner.d.ts.map +0 -1
  220. package/dist/pixiv-client/TargetSearchRunner.js.map +0 -1
  221. package/dist/pixiv-client/createPixivFlowClient.d.ts.map +0 -1
  222. package/dist/pixiv-client/createPixivFlowClient.js.map +0 -1
  223. package/dist/pixiv-client/query-mapper.d.ts.map +0 -1
  224. package/dist/pixiv-client/query-mapper.js.map +0 -1
  225. package/dist/pixiv-token-getter-adapter.d.ts.map +0 -1
  226. package/dist/pixiv-token-getter-adapter.js.map +0 -1
  227. package/dist/puppeteer-login-adapter/auth-code.d.ts.map +0 -1
  228. package/dist/puppeteer-login-adapter/auth-code.js.map +0 -1
  229. package/dist/puppeteer-login-adapter/browser-launch.d.ts.map +0 -1
  230. package/dist/puppeteer-login-adapter/browser-launch.js.map +0 -1
  231. package/dist/puppeteer-login-adapter/constants.d.ts.map +0 -1
  232. package/dist/puppeteer-login-adapter/constants.js.map +0 -1
  233. package/dist/puppeteer-login-adapter/environment.d.ts.map +0 -1
  234. package/dist/puppeteer-login-adapter/environment.js.map +0 -1
  235. package/dist/puppeteer-login-adapter/login-headless.d.ts.map +0 -1
  236. package/dist/puppeteer-login-adapter/login-headless.js.map +0 -1
  237. package/dist/puppeteer-login-adapter/login-interactive.d.ts.map +0 -1
  238. package/dist/puppeteer-login-adapter/login-interactive.js.map +0 -1
  239. package/dist/puppeteer-login-adapter/pkce.d.ts.map +0 -1
  240. package/dist/puppeteer-login-adapter/pkce.js.map +0 -1
  241. package/dist/puppeteer-login-adapter/proxy.d.ts.map +0 -1
  242. package/dist/puppeteer-login-adapter/proxy.js.map +0 -1
  243. package/dist/puppeteer-login-adapter/token-exchange.d.ts.map +0 -1
  244. package/dist/puppeteer-login-adapter/token-exchange.js.map +0 -1
  245. package/dist/puppeteer-login-adapter.d.ts.map +0 -1
  246. package/dist/puppeteer-login-adapter.js.map +0 -1
  247. package/dist/python-login-adapter/login-scripts.d.ts.map +0 -1
  248. package/dist/python-login-adapter/login-scripts.js.map +0 -1
  249. package/dist/python-login-adapter/proxy.d.ts.map +0 -1
  250. package/dist/python-login-adapter/proxy.js.map +0 -1
  251. package/dist/python-login-adapter/python-environment.d.ts.map +0 -1
  252. package/dist/python-login-adapter/python-environment.js.map +0 -1
  253. package/dist/python-login-adapter/response-converter.d.ts.map +0 -1
  254. package/dist/python-login-adapter/response-converter.js.map +0 -1
  255. package/dist/python-login-adapter.d.ts.map +0 -1
  256. package/dist/python-login-adapter.js.map +0 -1
  257. package/dist/random-download.d.ts.map +0 -1
  258. package/dist/random-download.js.map +0 -1
  259. package/dist/scheduler/MultiScheduleManager.d.ts.map +0 -1
  260. package/dist/scheduler/MultiScheduleManager.js.map +0 -1
  261. package/dist/scheduler/OccurrenceResolver.d.ts.map +0 -1
  262. package/dist/scheduler/OccurrenceResolver.js.map +0 -1
  263. package/dist/scheduler/ScheduleTriggerServer.d.ts.map +0 -1
  264. package/dist/scheduler/ScheduleTriggerServer.js.map +0 -1
  265. package/dist/scheduler/Scheduler.d.ts.map +0 -1
  266. package/dist/scheduler/Scheduler.js.map +0 -1
  267. package/dist/scheduler/SlotCoordinator.d.ts.map +0 -1
  268. package/dist/scheduler/SlotCoordinator.js.map +0 -1
  269. package/dist/scheduler/SlotStateMachine.d.ts.map +0 -1
  270. package/dist/scheduler/SlotStateMachine.js.map +0 -1
  271. package/dist/scheduler/TargetOutcome.d.ts.map +0 -1
  272. package/dist/scheduler/TargetOutcome.js.map +0 -1
  273. package/dist/scheduler/schedules.d.ts.map +0 -1
  274. package/dist/scheduler/schedules.js.map +0 -1
  275. package/dist/setup-wizard.d.ts.map +0 -1
  276. package/dist/setup-wizard.js.map +0 -1
  277. package/dist/storage/Database.d.ts.map +0 -1
  278. package/dist/storage/Database.js.map +0 -1
  279. package/dist/storage/DatabaseMigration.d.ts.map +0 -1
  280. package/dist/storage/DatabaseMigration.js.map +0 -1
  281. package/dist/storage/drivers/NodeSqliteDriver.d.ts.map +0 -1
  282. package/dist/storage/drivers/NodeSqliteDriver.js.map +0 -1
  283. package/dist/storage/drivers/SqliteDriver.d.ts.map +0 -1
  284. package/dist/storage/drivers/SqliteDriver.js.map +0 -1
  285. package/dist/storage/repositories/BaseRepository.d.ts.map +0 -1
  286. package/dist/storage/repositories/BaseRepository.js.map +0 -1
  287. package/dist/storage/repositories/ConfigHistoryRepository.d.ts.map +0 -1
  288. package/dist/storage/repositories/ConfigHistoryRepository.js.map +0 -1
  289. package/dist/storage/repositories/DeliveryRepository.d.ts.map +0 -1
  290. package/dist/storage/repositories/DeliveryRepository.js.map +0 -1
  291. package/dist/storage/repositories/DownloadQueryRepository.d.ts.map +0 -1
  292. package/dist/storage/repositories/DownloadQueryRepository.js.map +0 -1
  293. package/dist/storage/repositories/DownloadRepository.d.ts.map +0 -1
  294. package/dist/storage/repositories/DownloadRepository.js.map +0 -1
  295. package/dist/storage/repositories/DownloadStatsRepository.d.ts.map +0 -1
  296. package/dist/storage/repositories/DownloadStatsRepository.js.map +0 -1
  297. package/dist/storage/repositories/DownloadWriteRepository.d.ts.map +0 -1
  298. package/dist/storage/repositories/DownloadWriteRepository.js.map +0 -1
  299. package/dist/storage/repositories/ExecutionRepository.d.ts.map +0 -1
  300. package/dist/storage/repositories/ExecutionRepository.js.map +0 -1
  301. package/dist/storage/repositories/MetadataRepository.d.ts.map +0 -1
  302. package/dist/storage/repositories/MetadataRepository.js.map +0 -1
  303. package/dist/storage/repositories/OutboxRepository.d.ts.map +0 -1
  304. package/dist/storage/repositories/OutboxRepository.js.map +0 -1
  305. package/dist/storage/repositories/RateLimitStateRepository.d.ts.map +0 -1
  306. package/dist/storage/repositories/RateLimitStateRepository.js.map +0 -1
  307. package/dist/storage/repositories/SchedulerRepository.d.ts.map +0 -1
  308. package/dist/storage/repositories/SchedulerRepository.js.map +0 -1
  309. package/dist/storage/repositories/SlotRepository.d.ts.map +0 -1
  310. package/dist/storage/repositories/SlotRepository.js.map +0 -1
  311. package/dist/storage/repositories/TaskHistoryRepository.d.ts.map +0 -1
  312. package/dist/storage/repositories/TaskHistoryRepository.js.map +0 -1
  313. package/dist/storage/repositories/TokenRepository.d.ts.map +0 -1
  314. package/dist/storage/repositories/TokenRepository.js.map +0 -1
  315. package/dist/tags/TagDiscoveryService.d.ts.map +0 -1
  316. package/dist/tags/TagDiscoveryService.js.map +0 -1
  317. package/dist/tags/TagDiscoveryStore.d.ts.map +0 -1
  318. package/dist/tags/TagDiscoveryStore.js.map +0 -1
  319. package/dist/tags/TagPlanApplier.d.ts.map +0 -1
  320. package/dist/tags/TagPlanApplier.js.map +0 -1
  321. package/dist/tags/types.d.ts.map +0 -1
  322. package/dist/tags/types.js.map +0 -1
  323. package/dist/terminal-login/adapter-selector.d.ts.map +0 -1
  324. package/dist/terminal-login/adapter-selector.js.map +0 -1
  325. package/dist/terminal-login/constants.d.ts.map +0 -1
  326. package/dist/terminal-login/constants.js.map +0 -1
  327. package/dist/terminal-login/credentials.d.ts.map +0 -1
  328. package/dist/terminal-login/credentials.js.map +0 -1
  329. package/dist/terminal-login/output.d.ts.map +0 -1
  330. package/dist/terminal-login/output.js.map +0 -1
  331. package/dist/terminal-login/pixiv-terminal-auth.d.ts.map +0 -1
  332. package/dist/terminal-login/pixiv-terminal-auth.js.map +0 -1
  333. package/dist/terminal-login/terminal-login.d.ts.map +0 -1
  334. package/dist/terminal-login/terminal-login.js.map +0 -1
  335. package/dist/terminal-login/token-refresh.d.ts.map +0 -1
  336. package/dist/terminal-login/token-refresh.js.map +0 -1
  337. package/dist/terminal-login/types.d.ts.map +0 -1
  338. package/dist/terminal-login/types.js.map +0 -1
  339. package/dist/terminal-login.d.ts.map +0 -1
  340. package/dist/terminal-login.js.map +0 -1
  341. package/dist/topic/TopicCache.d.ts.map +0 -1
  342. package/dist/topic/TopicCache.js.map +0 -1
  343. package/dist/topic/TopicPipeline.d.ts.map +0 -1
  344. package/dist/topic/TopicPipeline.js.map +0 -1
  345. package/dist/topic/TopicResolver.d.ts.map +0 -1
  346. package/dist/topic/TopicResolver.js.map +0 -1
  347. package/dist/topic/TopicTagScorer.d.ts.map +0 -1
  348. package/dist/topic/TopicTagScorer.js.map +0 -1
  349. package/dist/topic/createTopicPipeline.d.ts.map +0 -1
  350. package/dist/topic/createTopicPipeline.js.map +0 -1
  351. package/dist/topic/types.d.ts.map +0 -1
  352. package/dist/topic/types.js.map +0 -1
  353. package/dist/utils/ai-detection.d.ts.map +0 -1
  354. package/dist/utils/ai-detection.js.map +0 -1
  355. package/dist/utils/concurrency.d.ts.map +0 -1
  356. package/dist/utils/concurrency.js.map +0 -1
  357. package/dist/utils/config-manager/config-normalization.d.ts.map +0 -1
  358. package/dist/utils/config-manager/config-normalization.js.map +0 -1
  359. package/dist/utils/config-manager/config-persistence.d.ts.map +0 -1
  360. package/dist/utils/config-manager/config-persistence.js.map +0 -1
  361. package/dist/utils/config-manager/config-repair.d.ts.map +0 -1
  362. package/dist/utils/config-manager/config-repair.js.map +0 -1
  363. package/dist/utils/config-manager/file-discovery.d.ts.map +0 -1
  364. package/dist/utils/config-manager/file-discovery.js.map +0 -1
  365. package/dist/utils/config-manager/file-operations.d.ts.map +0 -1
  366. package/dist/utils/config-manager/file-operations.js.map +0 -1
  367. package/dist/utils/config-manager.d.ts.map +0 -1
  368. package/dist/utils/config-manager.js.map +0 -1
  369. package/dist/utils/config-parser/field-definitions.d.ts.map +0 -1
  370. package/dist/utils/config-parser/field-definitions.js.map +0 -1
  371. package/dist/utils/config-parser/parser.d.ts.map +0 -1
  372. package/dist/utils/config-parser/parser.js.map +0 -1
  373. package/dist/utils/config-parser/reporter.d.ts.map +0 -1
  374. package/dist/utils/config-parser/reporter.js.map +0 -1
  375. package/dist/utils/config-parser/validator.d.ts.map +0 -1
  376. package/dist/utils/config-parser/validator.js.map +0 -1
  377. package/dist/utils/config-parser.d.ts.map +0 -1
  378. package/dist/utils/config-parser.js.map +0 -1
  379. package/dist/utils/config-path-migrator/auto-fix.d.ts.map +0 -1
  380. package/dist/utils/config-path-migrator/auto-fix.js.map +0 -1
  381. package/dist/utils/config-path-migrator/path-migration-core.d.ts.map +0 -1
  382. package/dist/utils/config-path-migrator/path-migration-core.js.map +0 -1
  383. package/dist/utils/config-path-migrator/storage-path-migration.d.ts.map +0 -1
  384. package/dist/utils/config-path-migrator/storage-path-migration.js.map +0 -1
  385. package/dist/utils/config-path-migrator.d.ts.map +0 -1
  386. package/dist/utils/config-path-migrator.js.map +0 -1
  387. package/dist/utils/config-validator-unified.d.ts.map +0 -1
  388. package/dist/utils/config-validator-unified.js.map +0 -1
  389. package/dist/utils/date-utils.d.ts.map +0 -1
  390. package/dist/utils/date-utils.js.map +0 -1
  391. package/dist/utils/directory-info.d.ts.map +0 -1
  392. package/dist/utils/directory-info.js.map +0 -1
  393. package/dist/utils/errors.d.ts.map +0 -1
  394. package/dist/utils/errors.js.map +0 -1
  395. package/dist/utils/fs.d.ts.map +0 -1
  396. package/dist/utils/fs.js.map +0 -1
  397. package/dist/utils/language-detection.d.ts.map +0 -1
  398. package/dist/utils/language-detection.js.map +0 -1
  399. package/dist/utils/login-helper.d.ts.map +0 -1
  400. package/dist/utils/login-helper.js.map +0 -1
  401. package/dist/utils/pixiv-date-utils.d.ts.map +0 -1
  402. package/dist/utils/pixiv-date-utils.js.map +0 -1
  403. package/dist/utils/pixiv-sort.d.ts.map +0 -1
  404. package/dist/utils/pixiv-sort.js.map +0 -1
  405. package/dist/utils/pixiv-url-parser.d.ts.map +0 -1
  406. package/dist/utils/pixiv-url-parser.js.map +0 -1
  407. package/dist/utils/pixiv-utils.d.ts.map +0 -1
  408. package/dist/utils/pixiv-utils.js.map +0 -1
  409. package/dist/utils/project-root.d.ts.map +0 -1
  410. package/dist/utils/project-root.js.map +0 -1
  411. package/dist/utils/redact.d.ts.map +0 -1
  412. package/dist/utils/redact.js.map +0 -1
  413. package/dist/utils/resource-manager.d.ts.map +0 -1
  414. package/dist/utils/resource-manager.js.map +0 -1
  415. package/dist/utils/stdin.d.ts.map +0 -1
  416. package/dist/utils/stdin.js.map +0 -1
  417. package/dist/utils/target-label.d.ts.map +0 -1
  418. package/dist/utils/target-label.js.map +0 -1
  419. package/dist/utils/token-maintenance.d.ts.map +0 -1
  420. package/dist/utils/token-maintenance.js.map +0 -1
  421. package/dist/utils/token-manager.d.ts.map +0 -1
  422. package/dist/utils/token-manager.js.map +0 -1
  423. package/dist/utils/token-revocation.d.ts.map +0 -1
  424. package/dist/utils/token-revocation.js.map +0 -1
  425. package/dist/utils/type-guards.d.ts.map +0 -1
  426. package/dist/utils/type-guards.js.map +0 -1
  427. package/dist/version.d.ts.map +0 -1
  428. package/dist/version.js.map +0 -1
  429. package/dist/webui/index.d.ts.map +0 -1
  430. package/dist/webui/index.js.map +0 -1
  431. package/dist/webui/ports.d.ts.map +0 -1
  432. package/dist/webui/ports.js.map +0 -1
  433. package/dist/webui/routes/auth.d.ts.map +0 -1
  434. package/dist/webui/routes/auth.js.map +0 -1
  435. package/dist/webui/routes/config-utils.d.ts.map +0 -1
  436. package/dist/webui/routes/config-utils.js.map +0 -1
  437. package/dist/webui/routes/config.d.ts.map +0 -1
  438. package/dist/webui/routes/config.js.map +0 -1
  439. package/dist/webui/routes/download.d.ts.map +0 -1
  440. package/dist/webui/routes/download.js.map +0 -1
  441. package/dist/webui/routes/files.d.ts.map +0 -1
  442. package/dist/webui/routes/files.js.map +0 -1
  443. package/dist/webui/routes/handlers/auth/auth-login-handler.d.ts.map +0 -1
  444. package/dist/webui/routes/handlers/auth/auth-login-handler.js.map +0 -1
  445. package/dist/webui/routes/handlers/auth/auth-logout-handler.d.ts.map +0 -1
  446. package/dist/webui/routes/handlers/auth/auth-logout-handler.js.map +0 -1
  447. package/dist/webui/routes/handlers/auth/auth-status-handler.d.ts.map +0 -1
  448. package/dist/webui/routes/handlers/auth/auth-status-handler.js.map +0 -1
  449. package/dist/webui/routes/handlers/auth/auth-token-handler.d.ts.map +0 -1
  450. package/dist/webui/routes/handlers/auth/auth-token-handler.js.map +0 -1
  451. package/dist/webui/routes/handlers/auth/auth-utils.d.ts.map +0 -1
  452. package/dist/webui/routes/handlers/auth/auth-utils.js.map +0 -1
  453. package/dist/webui/routes/handlers/auth-handlers.d.ts.map +0 -1
  454. package/dist/webui/routes/handlers/auth-handlers.js.map +0 -1
  455. package/dist/webui/routes/handlers/config-backup-handlers.d.ts.map +0 -1
  456. package/dist/webui/routes/handlers/config-backup-handlers.js.map +0 -1
  457. package/dist/webui/routes/handlers/config-basic-handlers.d.ts.map +0 -1
  458. package/dist/webui/routes/handlers/config-basic-handlers.js.map +0 -1
  459. package/dist/webui/routes/handlers/config-diagnostic-handlers.d.ts.map +0 -1
  460. package/dist/webui/routes/handlers/config-diagnostic-handlers.js.map +0 -1
  461. package/dist/webui/routes/handlers/config-files-handlers.d.ts.map +0 -1
  462. package/dist/webui/routes/handlers/config-files-handlers.js.map +0 -1
  463. package/dist/webui/routes/handlers/config-history-handlers.d.ts.map +0 -1
  464. package/dist/webui/routes/handlers/config-history-handlers.js.map +0 -1
  465. package/dist/webui/routes/handlers/download-basic-handlers.d.ts.map +0 -1
  466. package/dist/webui/routes/handlers/download-basic-handlers.js.map +0 -1
  467. package/dist/webui/routes/handlers/download-batch-handlers.d.ts.map +0 -1
  468. package/dist/webui/routes/handlers/download-batch-handlers.js.map +0 -1
  469. package/dist/webui/routes/handlers/download-incomplete-handlers.d.ts.map +0 -1
  470. package/dist/webui/routes/handlers/download-incomplete-handlers.js.map +0 -1
  471. package/dist/webui/routes/handlers/download-url-handlers.d.ts.map +0 -1
  472. package/dist/webui/routes/handlers/download-url-handlers.js.map +0 -1
  473. package/dist/webui/routes/handlers/files-list-handlers.d.ts.map +0 -1
  474. package/dist/webui/routes/handlers/files-list-handlers.js.map +0 -1
  475. package/dist/webui/routes/handlers/files-operations-handlers.d.ts.map +0 -1
  476. package/dist/webui/routes/handlers/files-operations-handlers.js.map +0 -1
  477. package/dist/webui/routes/handlers/logs-handlers.d.ts.map +0 -1
  478. package/dist/webui/routes/handlers/logs-handlers.js.map +0 -1
  479. package/dist/webui/routes/handlers/stats-handlers.d.ts.map +0 -1
  480. package/dist/webui/routes/handlers/stats-handlers.js.map +0 -1
  481. package/dist/webui/routes/logs.d.ts.map +0 -1
  482. package/dist/webui/routes/logs.js.map +0 -1
  483. package/dist/webui/routes/stats.d.ts.map +0 -1
  484. package/dist/webui/routes/stats.js.map +0 -1
  485. package/dist/webui/server/auth-middleware.d.ts.map +0 -1
  486. package/dist/webui/server/auth-middleware.js.map +0 -1
  487. package/dist/webui/server/server-middleware.d.ts.map +0 -1
  488. package/dist/webui/server/server-middleware.js.map +0 -1
  489. package/dist/webui/server/server-routes.d.ts.map +0 -1
  490. package/dist/webui/server/server-routes.js.map +0 -1
  491. package/dist/webui/server/server-static.d.ts.map +0 -1
  492. package/dist/webui/server/server-static.js.map +0 -1
  493. package/dist/webui/server/server-utils.d.ts.map +0 -1
  494. package/dist/webui/server/server-utils.js.map +0 -1
  495. package/dist/webui/server/server.d.ts.map +0 -1
  496. package/dist/webui/server/server.js.map +0 -1
  497. package/dist/webui/server/types.d.ts.map +0 -1
  498. package/dist/webui/server/types.js.map +0 -1
  499. package/dist/webui/services/DownloadTaskManager.d.ts.map +0 -1
  500. package/dist/webui/services/DownloadTaskManager.js.map +0 -1
  501. package/dist/webui/utils/config-validator.d.ts.map +0 -1
  502. package/dist/webui/utils/config-validator.js.map +0 -1
  503. package/dist/webui/utils/error-codes.d.ts.map +0 -1
  504. package/dist/webui/utils/error-codes.js.map +0 -1
  505. package/dist/webui/utils/task-serialize.d.ts.map +0 -1
  506. package/dist/webui/utils/task-serialize.js.map +0 -1
  507. package/dist/webui/websocket/DownloadStatus.d.ts.map +0 -1
  508. package/dist/webui/websocket/DownloadStatus.js.map +0 -1
  509. package/dist/webui/websocket/LogStream.d.ts.map +0 -1
  510. package/dist/webui/websocket/LogStream.js.map +0 -1
@@ -6,6 +6,19 @@ import { ScheduleRunOptions } from './OccurrenceResolver';
6
6
  * cap so a wedged download run cannot occupy the scheduler queue forever.
7
7
  */
8
8
  export declare const DEFAULT_SCHEDULE_TIMEOUT_MS: number;
9
+ /**
10
+ * How long a run may keep draining after its timeout fired before the scheduler
11
+ * gives up on it.
12
+ *
13
+ * Cancellation is cooperative, so a request that never returns (or a retry loop
14
+ * that ignores the abort) can outlive the timeout indefinitely. Awaiting that
15
+ * forever is what kept a timed-out run holding its Slot lease: the scheduler
16
+ * stayed `running`, the runtime kept heart-beating, and the occurrence could
17
+ * never be recovered. After this window the run is declared abandoned — the
18
+ * scheduler slot is freed and the runtime takes the Slot terminal — so a wedged
19
+ * job degrades into one recorded failure instead of a permanent hang.
20
+ */
21
+ export declare const ABORT_DRAIN_MS: number;
9
22
  /**
10
23
  * Optional integration hooks allowing the host command to provide real
11
24
  * accounting data and cooperative cancellation to the scheduler.
@@ -29,6 +42,18 @@ export interface JobFailure {
29
42
  consecutiveFailures: number;
30
43
  stopped: boolean;
31
44
  }
45
+ /**
46
+ * Reported when a run was still executing after its timeout AND the drain window
47
+ * expired, so it can never be awaited to completion. The host must treat the run
48
+ * as finished: stop renewing any lease it owns and take its Slot terminal, or a
49
+ * recovery sweep will re-dispatch the same occurrence while this run is alive.
50
+ */
51
+ export interface JobAbandoned {
52
+ scheduleId: string;
53
+ executionNumber: number;
54
+ errorMessage: string | null;
55
+ drainWindowMs: number;
56
+ }
32
57
  /** Admission is acquired before timeout/accounting starts. */
33
58
  export interface JobAdmissionController {
34
59
  acquire(scheduleId: string): Promise<JobLease | null>;
@@ -40,6 +65,7 @@ export declare class Scheduler {
40
65
  private readonly scheduleId;
41
66
  private readonly admission?;
42
67
  private readonly onFailure?;
68
+ private readonly onAbandoned?;
43
69
  private task;
44
70
  private job;
45
71
  private running;
@@ -47,9 +73,10 @@ export declare class Scheduler {
47
73
  private executionCount;
48
74
  private consecutiveFailures;
49
75
  private timeoutHandle;
76
+ private drainHandle;
50
77
  private stopped;
51
78
  private pending;
52
- constructor(config: SchedulerConfig, database?: Database | undefined, telemetry?: JobTelemetry | undefined, scheduleId?: string, admission?: JobAdmissionController | undefined, onFailure?: ((failure: JobFailure) => Promise<void> | void) | undefined);
79
+ constructor(config: SchedulerConfig, database?: Database | undefined, telemetry?: JobTelemetry | undefined, scheduleId?: string, admission?: JobAdmissionController | undefined, onFailure?: ((failure: JobFailure) => Promise<void> | void) | undefined, onAbandoned?: ((abandoned: JobAbandoned) => Promise<void> | void) | undefined);
53
80
  start(job: (options?: ScheduleRunOptions) => Promise<void>): void;
54
81
  /**
55
82
  * Arm the scheduler for external/manual triggers (runNow) WITHOUT registering
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.Scheduler = exports.DEFAULT_SCHEDULE_TIMEOUT_MS = void 0;
6
+ exports.Scheduler = exports.ABORT_DRAIN_MS = exports.DEFAULT_SCHEDULE_TIMEOUT_MS = void 0;
7
7
  const node_cron_1 = __importDefault(require("node-cron"));
8
8
  const logger_1 = require("../logger");
9
9
  const errors_1 = require("../utils/errors");
@@ -12,6 +12,19 @@ const errors_1 = require("../utils/errors");
12
12
  * cap so a wedged download run cannot occupy the scheduler queue forever.
13
13
  */
14
14
  exports.DEFAULT_SCHEDULE_TIMEOUT_MS = 30 * 60 * 1000;
15
+ /**
16
+ * How long a run may keep draining after its timeout fired before the scheduler
17
+ * gives up on it.
18
+ *
19
+ * Cancellation is cooperative, so a request that never returns (or a retry loop
20
+ * that ignores the abort) can outlive the timeout indefinitely. Awaiting that
21
+ * forever is what kept a timed-out run holding its Slot lease: the scheduler
22
+ * stayed `running`, the runtime kept heart-beating, and the occurrence could
23
+ * never be recovered. After this window the run is declared abandoned — the
24
+ * scheduler slot is freed and the runtime takes the Slot terminal — so a wedged
25
+ * job degrades into one recorded failure instead of a permanent hang.
26
+ */
27
+ exports.ABORT_DRAIN_MS = 30 * 1000;
15
28
  class Scheduler {
16
29
  config;
17
30
  database;
@@ -19,6 +32,7 @@ class Scheduler {
19
32
  scheduleId;
20
33
  admission;
21
34
  onFailure;
35
+ onAbandoned;
22
36
  task = null;
23
37
  job = null;
24
38
  running = false;
@@ -26,15 +40,17 @@ class Scheduler {
26
40
  executionCount = 0;
27
41
  consecutiveFailures = 0;
28
42
  timeoutHandle = null;
43
+ drainHandle = null;
29
44
  stopped = false;
30
45
  pending = false;
31
- constructor(config, database, telemetry, scheduleId = 'default', admission, onFailure) {
46
+ constructor(config, database, telemetry, scheduleId = 'default', admission, onFailure, onAbandoned) {
32
47
  this.config = config;
33
48
  this.database = database;
34
49
  this.telemetry = telemetry;
35
50
  this.scheduleId = scheduleId;
36
51
  this.admission = admission;
37
52
  this.onFailure = onFailure;
53
+ this.onAbandoned = onAbandoned;
38
54
  }
39
55
  start(job) {
40
56
  this.arm(job, true);
@@ -181,6 +197,11 @@ class Scheduler {
181
197
  scheduleId: this.scheduleId,
182
198
  });
183
199
  // Set up timeout if configured
200
+ let abandoned = false;
201
+ let resolveDrainExpired = null;
202
+ const drainExpired = new Promise((resolve) => {
203
+ resolveDrainExpired = resolve;
204
+ });
184
205
  if (this.config.timeout) {
185
206
  this.timeoutHandle = setTimeout(() => {
186
207
  if (this.running) {
@@ -194,15 +215,33 @@ class Scheduler {
194
215
  catch (cancelError) {
195
216
  logger_1.logger.warn('Failed to request job cancellation', { error: cancelError });
196
217
  }
197
- // Keep running=true until the aborted job actually settles so the
198
- // concurrent-run guard stays correct during the drain window.
218
+ // Bounded drain. requestCancel is cooperative, so this run is only
219
+ // guaranteed to be *asked* to stop; a request that ignores the abort
220
+ // would otherwise hold `running` (and, through the host, the Slot
221
+ // lease) for good. After the drain window the run is abandoned.
222
+ this.drainHandle = setTimeout(() => {
223
+ abandoned = true;
224
+ errorMessage =
225
+ `Execution timeout after ${this.config.timeout}ms; the job was still running after the ` +
226
+ `${exports.ABORT_DRAIN_MS}ms drain window and has been abandoned`;
227
+ logger_1.logger.error('Job did not settle within the drain window; abandoning it', {
228
+ scheduleId: this.scheduleId,
229
+ drainWindowMs: exports.ABORT_DRAIN_MS,
230
+ });
231
+ resolveDrainExpired?.();
232
+ }, exports.ABORT_DRAIN_MS);
233
+ this.drainHandle.unref?.();
234
+ // Keep running=true until the aborted job settles (or the drain
235
+ // expires) so the concurrent-run guard stays correct.
199
236
  }
200
237
  }, this.config.timeout);
238
+ this.timeoutHandle.unref?.();
201
239
  }
240
+ const jobPromise = this.executeWithTracking(job, (count) => {
241
+ itemsDownloaded = count;
242
+ }, triggerOptions);
202
243
  try {
203
- await this.executeWithTracking(job, (count) => {
204
- itemsDownloaded = count;
205
- });
244
+ await (this.config.timeout ? Promise.race([jobPromise, drainExpired]) : jobPromise);
206
245
  if (timeoutOccurred) {
207
246
  status = 'timeout';
208
247
  this.consecutiveFailures++;
@@ -245,6 +284,38 @@ class Scheduler {
245
284
  clearTimeout(this.timeoutHandle);
246
285
  this.timeoutHandle = null;
247
286
  }
287
+ if (this.drainHandle) {
288
+ clearTimeout(this.drainHandle);
289
+ this.drainHandle = null;
290
+ }
291
+ if (abandoned) {
292
+ // The job is still running somewhere. Its abort signal has been fired, so
293
+ // it unwinds at the next checkpoint, but we stop waiting for it: capture
294
+ // its eventual rejection (nothing else can) and let the host finish the
295
+ // bookkeeping it owns — stop the lease heartbeat and take the Slot
296
+ // terminal — so a recovery sweep cannot re-dispatch the same occurrence
297
+ // while this run is still alive.
298
+ void jobPromise.catch((error) => {
299
+ logger_1.logger.warn('Abandoned job settled after the drain window', {
300
+ scheduleId: this.scheduleId,
301
+ error: error instanceof Error ? error.message : String(error),
302
+ });
303
+ });
304
+ try {
305
+ await this.onAbandoned?.({
306
+ scheduleId: this.scheduleId,
307
+ executionNumber,
308
+ errorMessage,
309
+ drainWindowMs: exports.ABORT_DRAIN_MS,
310
+ });
311
+ }
312
+ catch (error) {
313
+ logger_1.logger.warn('Failed to report an abandoned job', {
314
+ scheduleId: this.scheduleId,
315
+ error: error instanceof Error ? error.message : String(error),
316
+ });
317
+ }
318
+ }
248
319
  const endTime = new Date();
249
320
  const duration = endTime.getTime() - startTime.getTime();
250
321
  // Log to database if available
@@ -298,6 +369,10 @@ class Scheduler {
298
369
  clearTimeout(this.timeoutHandle);
299
370
  this.timeoutHandle = null;
300
371
  }
372
+ if (this.drainHandle) {
373
+ clearTimeout(this.drainHandle);
374
+ this.drainHandle = null;
375
+ }
301
376
  logger_1.logger.info('Scheduler stopped', {
302
377
  totalExecutions: this.executionCount,
303
378
  consecutiveFailures: this.consecutiveFailures,
@@ -3,6 +3,19 @@ import { Database } from '../storage/Database';
3
3
  import { CellStatus, SlotItemRecord, SlotRecord, SlotStatus } from '../storage/repositories/SlotRepository';
4
4
  import { TriggerSource } from './OccurrenceResolver';
5
5
  import { TargetOutcome } from './TargetOutcome';
6
+ /**
7
+ * Execution-lease TTL and heartbeat cadence.
8
+ *
9
+ * These describe how fast a DEAD worker is noticed — they are deliberately
10
+ * unrelated to `schedule.timeout` (which caps how long a run may take). Tying
11
+ * the two together made a crashed worker hold its slot for up to 31 minutes
12
+ * before any other trigger could resume it. A live worker renews its lease every
13
+ * SLOT_HEARTBEAT_MS, so the worst-case recovery delay after a crash/restart is
14
+ * about SLOT_LEASE_TTL_MS. The TTL stays comfortably above the heartbeat so an
15
+ * occasional stalled event loop cannot make a healthy run look dead.
16
+ */
17
+ export declare const SLOT_LEASE_TTL_MS: number;
18
+ export declare const SLOT_HEARTBEAT_MS: number;
6
19
  /**
7
20
  * Durable execution context attached to a run. A scheduled occurrence always
8
21
  * has a slotId; an ad-hoc/manual run has none (it never touches the slot ledger).
@@ -62,13 +75,25 @@ export declare class SlotCoordinator {
62
75
  */
63
76
  resolveOccurrence(schedule: ScheduleConfig, config: StandaloneConfig, triggerSource: TriggerSource, at?: Date, requestedSlotName?: string): SlotResolveResult;
64
77
  /**
65
- * Open (or resume) an occurrence and snapshot its target membership on first
66
- * creation. A later config reload cannot add/remove cells for this occurrence.
78
+ * Durably open (or resume) an occurrence and snapshot its target membership
79
+ * on first creation. A later config reload cannot add/remove cells for this
80
+ * occurrence.
81
+ *
82
+ * This deliberately does NOT mark the slot `running`: the HTTP trigger calls
83
+ * it before dispatch so that a crash between "accepted" and "claimed" still
84
+ * leaves a durable `pending` row for reconciliation to pick up (see
85
+ * `markRunning` and MultiScheduleManager's recovery loop).
67
86
  */
68
- begin(slot: SlotContext, schedule: ScheduleConfig, targets: TargetConfig[]): {
87
+ prepare(slot: SlotContext, schedule: ScheduleConfig, targets: TargetConfig[]): {
69
88
  slotRec: SlotRecord;
70
89
  alreadyCompleted: boolean;
71
90
  };
91
+ /**
92
+ * Mark the occurrence as actually executing. Called by the worker that has
93
+ * already won the cross-process lease (never by the accepting adapter), so a
94
+ * slot is never reported `running` by a process that is not running it.
95
+ */
96
+ markRunning(slotId: string): void;
72
97
  /**
73
98
  * The targets that still need to run for this occurrence. Membership comes
74
99
  * from the materialized snapshot (stable), intersected with the targets the
@@ -1,9 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SlotCoordinator = void 0;
3
+ exports.SlotCoordinator = exports.SLOT_HEARTBEAT_MS = exports.SLOT_LEASE_TTL_MS = void 0;
4
4
  exports.timezoneForSchedule = timezoneForSchedule;
5
5
  const logger_1 = require("../logger");
6
6
  const OccurrenceResolver_1 = require("./OccurrenceResolver");
7
+ /**
8
+ * Execution-lease TTL and heartbeat cadence.
9
+ *
10
+ * These describe how fast a DEAD worker is noticed — they are deliberately
11
+ * unrelated to `schedule.timeout` (which caps how long a run may take). Tying
12
+ * the two together made a crashed worker hold its slot for up to 31 minutes
13
+ * before any other trigger could resume it. A live worker renews its lease every
14
+ * SLOT_HEARTBEAT_MS, so the worst-case recovery delay after a crash/restart is
15
+ * about SLOT_LEASE_TTL_MS. The TTL stays comfortably above the heartbeat so an
16
+ * occasional stalled event loop cannot make a healthy run look dead.
17
+ */
18
+ exports.SLOT_LEASE_TTL_MS = 3 * 60 * 1000;
19
+ exports.SLOT_HEARTBEAT_MS = 30 * 1000;
7
20
  /**
8
21
  * Owns the Schedule Slot ledger for one scheduler run. A Slot is one durable
9
22
  * execution occurrence of a Schedule (NOT a morning/evening row). It ensures
@@ -58,10 +71,16 @@ class SlotCoordinator {
58
71
  };
59
72
  }
60
73
  /**
61
- * Open (or resume) an occurrence and snapshot its target membership on first
62
- * creation. A later config reload cannot add/remove cells for this occurrence.
74
+ * Durably open (or resume) an occurrence and snapshot its target membership
75
+ * on first creation. A later config reload cannot add/remove cells for this
76
+ * occurrence.
77
+ *
78
+ * This deliberately does NOT mark the slot `running`: the HTTP trigger calls
79
+ * it before dispatch so that a crash between "accepted" and "claimed" still
80
+ * leaves a durable `pending` row for reconciliation to pick up (see
81
+ * `markRunning` and MultiScheduleManager's recovery loop).
63
82
  */
64
- begin(slot, schedule, targets) {
83
+ prepare(slot, schedule, targets) {
65
84
  const targetIds = targets.map((t) => t.id).filter((id) => Boolean(id));
66
85
  const { slot: slotRec, created } = this.database.slots.getOrCreateSlot(slot.slotId, {
67
86
  scheduleId: slot.scheduleId,
@@ -86,9 +105,16 @@ class SlotCoordinator {
86
105
  if (slotRec.status === 'success' || slotRec.status === 'partial') {
87
106
  return { slotRec, alreadyCompleted: true };
88
107
  }
89
- this.database.slots.markSlotStatus(slot.slotId, 'running');
90
108
  return { slotRec, alreadyCompleted: false };
91
109
  }
110
+ /**
111
+ * Mark the occurrence as actually executing. Called by the worker that has
112
+ * already won the cross-process lease (never by the accepting adapter), so a
113
+ * slot is never reported `running` by a process that is not running it.
114
+ */
115
+ markRunning(slotId) {
116
+ this.database.slots.markSlotStatus(slotId, 'running');
117
+ }
92
118
  /**
93
119
  * The targets that still need to run for this occurrence. Membership comes
94
120
  * from the materialized snapshot (stable), intersected with the targets the
@@ -115,6 +115,34 @@ export declare class SlotRepository extends BaseRepository {
115
115
  };
116
116
  /** Slots whose lease expired while still non-terminal (crashed workers). */
117
117
  slotsWithStaleLease(now?: number): string[];
118
+ /**
119
+ * Non-terminal slots no live worker currently owns, and which therefore should
120
+ * be re-dispatched.
121
+ *
122
+ * Two distinct situations qualify, and both must be recovered:
123
+ * - `lease_until <= now`: a worker claimed the slot and then died (crash, OOM,
124
+ * machine stop, redeploy). Its heartbeat stopped, so the lease expired.
125
+ * - `lease_until IS NULL`: a durable slot was recorded (accepted) but no worker
126
+ * ever claimed it — e.g. the process died between "occurrence recorded" and
127
+ * "lease claimed". Nothing else will ever pick that up.
128
+ *
129
+ * Callers must NOT clear the lease from here. Recovery re-DISPATCHES the slot
130
+ * and the atomic CAS in `claimSlotLease` still elects the single winner, so a
131
+ * merely slow (but healthy) owner that heartbeats mid-scan cannot be robbed by
132
+ * a read-then-clear race.
133
+ */
134
+ recoverableSlots(now?: number): SlotRecord[];
135
+ /**
136
+ * Non-terminal Slots (pending/running): work this ledger still owes, whether
137
+ * or not a live worker currently holds their lease.
138
+ *
139
+ * Deliberately NOT `recoverableSlots()`. That query excludes slots with a live
140
+ * lease — i.e. exactly the slots being executed right now — so using it as an
141
+ * "is anything still running?" probe reports idle in the middle of a run. The
142
+ * external-worker lifecycle needs to answer the opposite question, so it asks
143
+ * this one instead.
144
+ */
145
+ countActiveSlots(): number;
118
146
  /** Record an error without changing terminality (retryable failure keeps the cell resumable). */
119
147
  setCellError(slotId: string, targetId: string, error: string): void;
120
148
  /** Work ids already locked in THIS slot (to stop two cells taking the same work). */
@@ -211,12 +211,50 @@ class SlotRepository extends BaseRepository_1.BaseRepository {
211
211
  }
212
212
  /** Slots whose lease expired while still non-terminal (crashed workers). */
213
213
  slotsWithStaleLease(now = Date.now()) {
214
+ return this.recoverableSlots(now)
215
+ .filter((slot) => slot.leaseUntil !== null)
216
+ .map((slot) => slot.id);
217
+ }
218
+ /**
219
+ * Non-terminal slots no live worker currently owns, and which therefore should
220
+ * be re-dispatched.
221
+ *
222
+ * Two distinct situations qualify, and both must be recovered:
223
+ * - `lease_until <= now`: a worker claimed the slot and then died (crash, OOM,
224
+ * machine stop, redeploy). Its heartbeat stopped, so the lease expired.
225
+ * - `lease_until IS NULL`: a durable slot was recorded (accepted) but no worker
226
+ * ever claimed it — e.g. the process died between "occurrence recorded" and
227
+ * "lease claimed". Nothing else will ever pick that up.
228
+ *
229
+ * Callers must NOT clear the lease from here. Recovery re-DISPATCHES the slot
230
+ * and the atomic CAS in `claimSlotLease` still elects the single winner, so a
231
+ * merely slow (but healthy) owner that heartbeats mid-scan cannot be robbed by
232
+ * a read-then-clear race.
233
+ */
234
+ recoverableSlots(now = Date.now()) {
214
235
  const rows = this.db
215
- .prepare(`SELECT id FROM schedule_slots
216
- WHERE lease_until IS NOT NULL AND lease_until < @now
217
- AND status IN ('pending','running')`)
236
+ .prepare(`SELECT * FROM schedule_slots
237
+ WHERE status IN ('pending','running')
238
+ AND (lease_until IS NULL OR lease_until <= @now)
239
+ ORDER BY COALESCE(occurrence_at, 0) ASC`)
218
240
  .all({ now });
219
- return rows.map((r) => r.id);
241
+ return rows.map((r) => this.toSlot(r));
242
+ }
243
+ /**
244
+ * Non-terminal Slots (pending/running): work this ledger still owes, whether
245
+ * or not a live worker currently holds their lease.
246
+ *
247
+ * Deliberately NOT `recoverableSlots()`. That query excludes slots with a live
248
+ * lease — i.e. exactly the slots being executed right now — so using it as an
249
+ * "is anything still running?" probe reports idle in the middle of a run. The
250
+ * external-worker lifecycle needs to answer the opposite question, so it asks
251
+ * this one instead.
252
+ */
253
+ countActiveSlots() {
254
+ const row = this.db
255
+ .prepare(`SELECT COUNT(*) n FROM schedule_slots WHERE status IN ('pending','running')`)
256
+ .get();
257
+ return row.n;
220
258
  }
221
259
  /** Record an error without changing terminality (retryable failure keeps the cell resumable). */
222
260
  setCellError(slotId, targetId, error) {
@@ -21,7 +21,21 @@ export declare class TopicPipeline {
21
21
  private readonly client;
22
22
  private readonly resolver;
23
23
  private readonly requestDelayMs;
24
- constructor(client: TopicClient, resolver: TopicResolver, requestDelayMs?: number);
24
+ /**
25
+ * Run-scoped cancellation. Candidate acquisition is the longest stretch of
26
+ * network work in a run, so it must observe the same abort as the download
27
+ * pipeline; otherwise a cancelled run keeps searching tags and can never
28
+ * settle, which is what let a timed-out run hold its slot lease forever.
29
+ */
30
+ private readonly signal?;
31
+ constructor(client: TopicClient, resolver: TopicResolver, requestDelayMs?: number,
32
+ /**
33
+ * Run-scoped cancellation. Candidate acquisition is the longest stretch of
34
+ * network work in a run, so it must observe the same abort as the download
35
+ * pipeline; otherwise a cancelled run keeps searching tags and can never
36
+ * settle, which is what let a timed-out run hold its slot lease forever.
37
+ */
38
+ signal?: AbortSignal | undefined);
25
39
  selectWorks<T extends WorkLike>(target: TargetConfig, contentType: TopicContentType, day: string, limit: number, discovery: TopicDiscoveryOptions, collect: TopicCollectOptions): Promise<{
26
40
  works: T[];
27
41
  selection: TopicSelection;
@@ -5,6 +5,7 @@ const promises_1 = require("node:timers/promises");
5
5
  const logger_1 = require("../logger");
6
6
  const pixiv_utils_1 = require("../utils/pixiv-utils");
7
7
  const ai_detection_1 = require("../utils/ai-detection");
8
+ const errors_1 = require("../utils/errors");
8
9
  const COLLECT_DEFAULTS = { maxPerTag: 40, maxCandidates: 250, minMetadataScore: 0.35 };
9
10
  /** Small, platform-level tags that are never evidence of topic membership. */
10
11
  const STOP_TAGS = new Set([
@@ -24,10 +25,19 @@ class TopicPipeline {
24
25
  client;
25
26
  resolver;
26
27
  requestDelayMs;
27
- constructor(client, resolver, requestDelayMs = 500) {
28
+ signal;
29
+ constructor(client, resolver, requestDelayMs = 500,
30
+ /**
31
+ * Run-scoped cancellation. Candidate acquisition is the longest stretch of
32
+ * network work in a run, so it must observe the same abort as the download
33
+ * pipeline; otherwise a cancelled run keeps searching tags and can never
34
+ * settle, which is what let a timed-out run hold its slot lease forever.
35
+ */
36
+ signal) {
28
37
  this.client = client;
29
38
  this.resolver = resolver;
30
39
  this.requestDelayMs = requestDelayMs;
40
+ this.signal = signal;
31
41
  }
32
42
  async selectWorks(target, contentType, day, limit, discovery, collect) {
33
43
  const topic = (target.topic ?? '').trim();
@@ -44,6 +54,9 @@ class TopicPipeline {
44
54
  for (let i = 0; i < tagNames.length; i++) {
45
55
  if (byId.size >= maxCandidates)
46
56
  break;
57
+ // Cancellation is checked between tags, so a cancelled run stops issuing
58
+ // new searches even when the aborted request itself had already returned.
59
+ (0, errors_1.throwIfAborted)(this.signal, 'topic collection cancelled');
47
60
  const tag = tagNames[i];
48
61
  const works = await this.searchDay(contentType, tag, day, maxPerTag, includeR18);
49
62
  rawCount += works.length;
@@ -99,7 +112,7 @@ class TopicPipeline {
99
112
  }
100
113
  async searchDay(contentType, tag, day, limit, includeR18) {
101
114
  try {
102
- const opts = { startDate: day, endDate: day, includeR18 };
115
+ const opts = { startDate: day, endDate: day, includeR18, signal: this.signal };
103
116
  const works = contentType === 'illustration'
104
117
  ? await this.client.searchIllustrationsForTags(tag, limit, opts)
105
118
  : await this.client.searchNovelsForTags(tag, limit, opts);
@@ -107,6 +120,9 @@ class TopicPipeline {
107
120
  return works.filter((w) => this.onDay(w.create_date, day));
108
121
  }
109
122
  catch (error) {
123
+ // A cancellation must not be degraded into "no results for this tag": that
124
+ // would silently continue the cancelled run across the remaining tags.
125
+ (0, errors_1.rethrowIfCancelled)(error, this.signal);
110
126
  logger_1.logger.warn('[TopicCollector] search failed tag=' + tag + ' type=' + contentType + ': ' + (error instanceof Error ? error.message : String(error)));
111
127
  return [];
112
128
  }
@@ -12,8 +12,12 @@ export declare class TopicResolver {
12
12
  private readonly client;
13
13
  private readonly cache;
14
14
  private readonly requestDelayMs;
15
+ /** Run-scoped cancellation, forwarded to every discovery request. */
16
+ private readonly signal?;
15
17
  private readonly scorer;
16
- constructor(client: TopicClient, cache: TopicCache, requestDelayMs?: number);
18
+ constructor(client: TopicClient, cache: TopicCache, requestDelayMs?: number,
19
+ /** Run-scoped cancellation, forwarded to every discovery request. */
20
+ signal?: AbortSignal | undefined);
17
21
  /** Resolve tags for a topic+type, using cache when fresh. Never throws for
18
22
  * discovery errors: it always returns at least the seed tag. */
19
23
  resolve(topic: string, contentType: TopicContentType, options?: TopicDiscoveryOptions): Promise<{
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TopicResolver = void 0;
4
4
  const promises_1 = require("node:timers/promises");
5
5
  const logger_1 = require("../logger");
6
+ const errors_1 = require("../utils/errors");
6
7
  const TopicTagScorer_1 = require("./TopicTagScorer");
7
8
  /** Platform-wide tags never worth searching as topic keys or scoring metadata. */
8
9
  const STOP_TAGS = new Set([
@@ -29,11 +30,15 @@ class TopicResolver {
29
30
  client;
30
31
  cache;
31
32
  requestDelayMs;
33
+ signal;
32
34
  scorer = new TopicTagScorer_1.TopicTagScorer();
33
- constructor(client, cache, requestDelayMs = 500) {
35
+ constructor(client, cache, requestDelayMs = 500,
36
+ /** Run-scoped cancellation, forwarded to every discovery request. */
37
+ signal) {
34
38
  this.client = client;
35
39
  this.cache = cache;
36
40
  this.requestDelayMs = requestDelayMs;
41
+ this.signal = signal;
37
42
  }
38
43
  /** Resolve tags for a topic+type, using cache when fresh. Never throws for
39
44
  * discovery errors: it always returns at least the seed tag. */
@@ -117,19 +122,40 @@ class TopicResolver {
117
122
  }
118
123
  /** Serial, bounded fetches: autocomplete → topic sample → small background. */
119
124
  async gatherSamples(seed, contentType, sampleWorks, includeR18) {
120
- const suggested = await this.client.getTagAutocomplete(seed).catch(() => []);
125
+ (0, errors_1.throwIfAborted)(this.signal, 'topic resolution cancelled');
126
+ // Discovery degrades to "no suggestions" / "no background" on ordinary
127
+ // failures, but a cancellation must abort the run rather than quietly
128
+ // continue against a stopped pipeline.
129
+ const suggested = await this.client
130
+ .getTagAutocomplete(seed, { signal: this.signal })
131
+ .catch((error) => {
132
+ (0, errors_1.rethrowIfCancelled)(error, this.signal);
133
+ return [];
134
+ });
121
135
  if (this.requestDelayMs > 0)
122
136
  await (0, promises_1.setTimeout)(this.requestDelayMs);
137
+ (0, errors_1.throwIfAborted)(this.signal, 'topic resolution cancelled');
123
138
  const topicWorks = contentType === 'illustration'
124
- ? await this.client.searchIllustrationsForTags(seed, sampleWorks, { includeR18 })
125
- : await this.client.searchNovelsForTags(seed, sampleWorks, { includeR18 });
139
+ ? await this.client.searchIllustrationsForTags(seed, sampleWorks, { includeR18, signal: this.signal })
140
+ : await this.client.searchNovelsForTags(seed, sampleWorks, { includeR18, signal: this.signal });
126
141
  // A small, cheap background sample (a common platform tag) estimates how
127
142
  // generic a co-occurring tag is. Bounded to keep requests/memory low.
128
143
  if (this.requestDelayMs > 0)
129
144
  await (0, promises_1.setTimeout)(this.requestDelayMs);
145
+ (0, errors_1.throwIfAborted)(this.signal, 'topic resolution cancelled');
130
146
  const backgroundWorks = contentType === 'illustration'
131
- ? await this.client.searchIllustrationsForTags('イラスト', 40, { includeR18 }).catch(() => [])
132
- : await this.client.searchNovelsForTags('小説', 40, { includeR18 }).catch(() => []);
147
+ ? await this.client
148
+ .searchIllustrationsForTags('イラスト', 40, { includeR18, signal: this.signal })
149
+ .catch((error) => {
150
+ (0, errors_1.rethrowIfCancelled)(error, this.signal);
151
+ return [];
152
+ })
153
+ : await this.client
154
+ .searchNovelsForTags('小説', 40, { includeR18, signal: this.signal })
155
+ .catch((error) => {
156
+ (0, errors_1.rethrowIfCancelled)(error, this.signal);
157
+ return [];
158
+ });
133
159
  return [suggested, topicWorks, backgroundWorks];
134
160
  }
135
161
  seedOnly(seed, contentType, cacheDays) {
@@ -9,8 +9,8 @@ export type TopicPipelineFactory = () => TopicPipeline;
9
9
  type DbPathSource = string | {
10
10
  getDatabasePath(): string;
11
11
  } | undefined;
12
- export declare function createTopicPipeline(client: IPixivClient, databasePath: DbPathSource, requestDelayMs?: number): TopicPipeline;
12
+ export declare function createTopicPipeline(client: IPixivClient, databasePath: DbPathSource, requestDelayMs?: number, signal?: AbortSignal): TopicPipeline;
13
13
  /** Lazily builds one shared pipeline per download run (cache + resolver). */
14
- export declare function createTopicPipelineFactory(client: IPixivClient, databasePath: DbPathSource, requestDelayMs?: number): TopicPipelineFactory;
14
+ export declare function createTopicPipelineFactory(client: IPixivClient, databasePath: DbPathSource, requestDelayMs?: number, signal?: AbortSignal): TopicPipelineFactory;
15
15
  export {};
16
16
  //# sourceMappingURL=createTopicPipeline.d.ts.map
@@ -15,18 +15,18 @@ function resolveDbPath(source) {
15
15
  const absolute = raw && (0, node_path_1.isAbsolute)(raw) ? raw : (0, node_path_1.resolve)(process.cwd(), raw ?? 'data/pixiv-downloader.db');
16
16
  return absolute;
17
17
  }
18
- function createTopicPipeline(client, databasePath, requestDelayMs = 500) {
18
+ function createTopicPipeline(client, databasePath, requestDelayMs = 500, signal) {
19
19
  const resolved = resolveDbPath(databasePath);
20
20
  const cache = new TopicCache_1.TopicCache((0, node_path_1.resolve)((0, node_path_1.dirname)(resolved), 'topic-cache'));
21
- const resolver = new TopicResolver_1.TopicResolver(client, cache, requestDelayMs);
22
- return new TopicPipeline_1.TopicPipeline(client, resolver, requestDelayMs);
21
+ const resolver = new TopicResolver_1.TopicResolver(client, cache, requestDelayMs, signal);
22
+ return new TopicPipeline_1.TopicPipeline(client, resolver, requestDelayMs, signal);
23
23
  }
24
24
  /** Lazily builds one shared pipeline per download run (cache + resolver). */
25
- function createTopicPipelineFactory(client, databasePath, requestDelayMs = 500) {
25
+ function createTopicPipelineFactory(client, databasePath, requestDelayMs = 500, signal) {
26
26
  let instance;
27
27
  return () => {
28
28
  if (!instance)
29
- instance = createTopicPipeline(client, databasePath, requestDelayMs);
29
+ instance = createTopicPipeline(client, databasePath, requestDelayMs, signal);
30
30
  return instance;
31
31
  };
32
32
  }
@@ -67,7 +67,9 @@ export interface TopicCandidate {
67
67
  }
68
68
  /** Minimal surface the resolver/collector need from the Pixiv client. */
69
69
  export interface TopicClient {
70
- getTagAutocomplete(seed: string): Promise<Array<{
70
+ getTagAutocomplete(seed: string, options?: {
71
+ signal?: AbortSignal;
72
+ }): Promise<Array<{
71
73
  name: string;
72
74
  translated_name?: string;
73
75
  }>>;
@@ -75,11 +77,13 @@ export interface TopicClient {
75
77
  startDate?: string;
76
78
  endDate?: string;
77
79
  includeR18?: boolean;
80
+ signal?: AbortSignal;
78
81
  }): Promise<Array<WorkLike>>;
79
82
  searchNovelsForTags(seed: string, limit: number, options?: {
80
83
  startDate?: string;
81
84
  endDate?: string;
82
85
  includeR18?: boolean;
86
+ signal?: AbortSignal;
83
87
  }): Promise<Array<WorkLike>>;
84
88
  }
85
89
  export interface WorkLike {