cursedbelt-server 1.0.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 (606) hide show
  1. package/LICENSE +15 -0
  2. package/dist/server/activity/index.d.ts +36 -0
  3. package/dist/server/activity/index.js +36 -0
  4. package/dist/server/activity/migrations.d.ts +21 -0
  5. package/dist/server/activity/migrations.js +55 -0
  6. package/dist/server/activity/shareSource.d.ts +11 -0
  7. package/dist/server/activity/shareSource.js +45 -0
  8. package/dist/server/activity/store.d.ts +123 -0
  9. package/dist/server/activity/store.js +178 -0
  10. package/dist/server/app.d.ts +56 -0
  11. package/dist/server/app.js +45 -0
  12. package/dist/server/audit/index.d.ts +2 -0
  13. package/dist/server/audit/index.js +2 -0
  14. package/dist/server/audit/migrations.d.ts +2 -0
  15. package/dist/server/audit/migrations.js +79 -0
  16. package/dist/server/audit/sqliteSink.d.ts +36 -0
  17. package/dist/server/audit/sqliteSink.js +95 -0
  18. package/dist/server/audit/sqliteSink.test.d.ts +1 -0
  19. package/dist/server/audit/sqliteSink.test.js +180 -0
  20. package/dist/server/auth/cookie.d.ts +22 -0
  21. package/dist/server/auth/cookie.js +35 -0
  22. package/dist/server/auth/csrf.d.ts +42 -0
  23. package/dist/server/auth/csrf.js +36 -0
  24. package/dist/server/auth/fileTokenSigner.d.ts +10 -0
  25. package/dist/server/auth/fileTokenSigner.js +22 -0
  26. package/dist/server/auth/getOnly.d.ts +20 -0
  27. package/dist/server/auth/getOnly.js +27 -0
  28. package/dist/server/auth/index.d.ts +12 -0
  29. package/dist/server/auth/index.js +15 -0
  30. package/dist/server/auth/jwt.d.ts +18 -0
  31. package/dist/server/auth/jwt.js +56 -0
  32. package/dist/server/auth/loginThrottle.d.ts +118 -0
  33. package/dist/server/auth/loginThrottle.js +204 -0
  34. package/dist/server/auth/middleware.d.ts +14 -0
  35. package/dist/server/auth/middleware.js +47 -0
  36. package/dist/server/auth/password.d.ts +14 -0
  37. package/dist/server/auth/password.js +65 -0
  38. package/dist/server/auth/sessionStore.d.ts +19 -0
  39. package/dist/server/auth/sessionStore.js +221 -0
  40. package/dist/server/auth/totp.d.ts +34 -0
  41. package/dist/server/auth/totp.js +81 -0
  42. package/dist/server/auth/types.d.ts +133 -0
  43. package/dist/server/auth/types.js +1 -0
  44. package/dist/server/auth/vault.d.ts +27 -0
  45. package/dist/server/auth/vault.js +57 -0
  46. package/dist/server/context.d.ts +35 -0
  47. package/dist/server/context.js +1 -0
  48. package/dist/server/ctgr.d.ts +32 -0
  49. package/dist/server/ctgr.js +69 -0
  50. package/dist/server/db/kysely.d.ts +14 -0
  51. package/dist/server/db/kysely.js +16 -0
  52. package/dist/server/db/plumbingTypes.d.ts +116 -0
  53. package/dist/server/db/plumbingTypes.js +12 -0
  54. package/dist/server/dropzone/duplicates.d.ts +82 -0
  55. package/dist/server/dropzone/duplicates.js +90 -0
  56. package/dist/server/dropzone/index.d.ts +13 -0
  57. package/dist/server/dropzone/index.js +13 -0
  58. package/dist/server/dropzone/scan.d.ts +166 -0
  59. package/dist/server/dropzone/scan.js +272 -0
  60. package/dist/server/errors.d.ts +7 -0
  61. package/dist/server/errors.js +7 -0
  62. package/dist/server/guard/argon2Binding.d.ts +97 -0
  63. package/dist/server/guard/argon2Binding.js +138 -0
  64. package/dist/server/guard/index.d.ts +4 -0
  65. package/dist/server/guard/index.js +6 -0
  66. package/dist/server/guard/kdfWorker.d.ts +101 -0
  67. package/dist/server/guard/kdfWorker.js +301 -0
  68. package/dist/server/guard/kdfWorkerEntry.d.ts +1 -0
  69. package/dist/server/guard/kdfWorkerEntry.js +15 -0
  70. package/dist/server/guard/passwordVerifier.d.ts +65 -0
  71. package/dist/server/guard/passwordVerifier.js +122 -0
  72. package/dist/server/guard/revocationStore.d.ts +132 -0
  73. package/dist/server/guard/revocationStore.js +211 -0
  74. package/dist/server/guard/sessionSigner.d.ts +132 -0
  75. package/dist/server/guard/sessionSigner.js +303 -0
  76. package/dist/server/guard/verifier.d.ts +21 -0
  77. package/dist/server/guard/verifier.js +1 -0
  78. package/dist/server/incidents/channels.d.ts +161 -0
  79. package/dist/server/incidents/channels.js +357 -0
  80. package/dist/server/incidents/checks.d.ts +61 -0
  81. package/dist/server/incidents/checks.js +55 -0
  82. package/dist/server/incidents/index.d.ts +51 -0
  83. package/dist/server/incidents/index.js +51 -0
  84. package/dist/server/incidents/migrations.d.ts +20 -0
  85. package/dist/server/incidents/migrations.js +58 -0
  86. package/dist/server/incidents/notifier.d.ts +79 -0
  87. package/dist/server/incidents/notifier.js +141 -0
  88. package/dist/server/incidents/reporter.d.ts +60 -0
  89. package/dist/server/incidents/reporter.js +49 -0
  90. package/dist/server/incidents/routes.d.ts +91 -0
  91. package/dist/server/incidents/routes.js +202 -0
  92. package/dist/server/incidents/store.d.ts +75 -0
  93. package/dist/server/incidents/store.js +176 -0
  94. package/dist/server/incidents/types.d.ts +107 -0
  95. package/dist/server/incidents/types.js +32 -0
  96. package/dist/server/index.d.ts +25 -0
  97. package/dist/server/index.js +53 -0
  98. package/dist/server/jobs/fireQueue.d.ts +12 -0
  99. package/dist/server/jobs/fireQueue.js +37 -0
  100. package/dist/server/jobs/index.d.ts +6 -0
  101. package/dist/server/jobs/index.js +10 -0
  102. package/dist/server/jobs/logger.d.ts +10 -0
  103. package/dist/server/jobs/logger.js +14 -0
  104. package/dist/server/jobs/plainjobAdapter.d.ts +19 -0
  105. package/dist/server/jobs/plainjobAdapter.js +48 -0
  106. package/dist/server/jobs/polling.d.ts +17 -0
  107. package/dist/server/jobs/polling.js +79 -0
  108. package/dist/server/jobs/runner.d.ts +24 -0
  109. package/dist/server/jobs/runner.js +186 -0
  110. package/dist/server/jobs/timeout.d.ts +28 -0
  111. package/dist/server/jobs/timeout.js +47 -0
  112. package/dist/server/jobs/types.d.ts +167 -0
  113. package/dist/server/jobs/types.js +12 -0
  114. package/dist/server/master-lock/guard.d.ts +53 -0
  115. package/dist/server/master-lock/guard.js +205 -0
  116. package/dist/server/master-lock/index.d.ts +21 -0
  117. package/dist/server/master-lock/index.js +21 -0
  118. package/dist/server/master-lock/lockPage.d.ts +34 -0
  119. package/dist/server/master-lock/lockPage.js +281 -0
  120. package/dist/server/master-lock/masterLock.d.ts +294 -0
  121. package/dist/server/master-lock/masterLock.js +523 -0
  122. package/dist/server/master-lock/principals.d.ts +76 -0
  123. package/dist/server/master-lock/principals.js +157 -0
  124. package/dist/server/master-lock/seed.d.ts +47 -0
  125. package/dist/server/master-lock/seed.js +93 -0
  126. package/dist/server/master-lock/store.d.ts +19 -0
  127. package/dist/server/master-lock/store.js +20 -0
  128. package/dist/server/media-bun/image.d.ts +7 -0
  129. package/dist/server/media-bun/image.js +96 -0
  130. package/dist/server/media-bun/index.d.ts +4 -0
  131. package/dist/server/media-bun/index.js +10 -0
  132. package/dist/server/media-bun/streaming.d.ts +86 -0
  133. package/dist/server/media-bun/streaming.js +237 -0
  134. package/dist/server/media-bun/video.d.ts +35 -0
  135. package/dist/server/media-bun/video.js +181 -0
  136. package/dist/server/metrics/metricsBuffer.d.ts +39 -0
  137. package/dist/server/metrics/metricsBuffer.js +36 -0
  138. package/dist/server/metrics/normalizeRoute.d.ts +5 -0
  139. package/dist/server/metrics/normalizeRoute.js +58 -0
  140. package/dist/server/middleware/correlationId.d.ts +9 -0
  141. package/dist/server/middleware/correlationId.js +20 -0
  142. package/dist/server/middleware/corsAllowlist.d.ts +38 -0
  143. package/dist/server/middleware/corsAllowlist.js +37 -0
  144. package/dist/server/middleware/errorEnvelope.d.ts +17 -0
  145. package/dist/server/middleware/errorEnvelope.js +32 -0
  146. package/dist/server/middleware/index.d.ts +9 -0
  147. package/dist/server/middleware/index.js +10 -0
  148. package/dist/server/middleware/localOrigin.d.ts +71 -0
  149. package/dist/server/middleware/localOrigin.js +119 -0
  150. package/dist/server/middleware/rateLimit.d.ts +56 -0
  151. package/dist/server/middleware/rateLimit.js +86 -0
  152. package/dist/server/middleware/requestLogger.d.ts +29 -0
  153. package/dist/server/middleware/requestLogger.js +47 -0
  154. package/dist/server/middleware/securityHeaders.d.ts +29 -0
  155. package/dist/server/middleware/securityHeaders.js +44 -0
  156. package/dist/server/middleware/zValidator.d.ts +29 -0
  157. package/dist/server/middleware/zValidator.js +57 -0
  158. package/dist/server/migration/runner.d.ts +42 -0
  159. package/dist/server/migration/runner.js +70 -0
  160. package/dist/server/migration/types.d.ts +16 -0
  161. package/dist/server/migration/types.js +1 -0
  162. package/dist/server/net/outboundGuard.d.ts +30 -0
  163. package/dist/server/net/outboundGuard.js +199 -0
  164. package/dist/server/notifications/hub.d.ts +67 -0
  165. package/dist/server/notifications/hub.js +187 -0
  166. package/dist/server/notifications/index.d.ts +47 -0
  167. package/dist/server/notifications/index.js +46 -0
  168. package/dist/server/notifications/migrations.d.ts +13 -0
  169. package/dist/server/notifications/migrations.js +20 -0
  170. package/dist/server/notifications/routes.d.ts +58 -0
  171. package/dist/server/notifications/routes.js +113 -0
  172. package/dist/server/notifications/service.d.ts +58 -0
  173. package/dist/server/notifications/service.js +41 -0
  174. package/dist/server/paths/appDataHome.d.ts +19 -0
  175. package/dist/server/paths/appDataHome.js +68 -0
  176. package/dist/server/private-media/crypto.d.ts +37 -0
  177. package/dist/server/private-media/crypto.js +111 -0
  178. package/dist/server/private-media/index-store.d.ts +31 -0
  179. package/dist/server/private-media/index-store.js +36 -0
  180. package/dist/server/private-media/mountPrivateRoutes.d.ts +26 -0
  181. package/dist/server/private-media/mountPrivateRoutes.js +219 -0
  182. package/dist/server/private-media/pipeline.d.ts +37 -0
  183. package/dist/server/private-media/pipeline.js +97 -0
  184. package/dist/server/private-media/unlock.d.ts +52 -0
  185. package/dist/server/private-media/unlock.js +55 -0
  186. package/dist/server/resume/index.d.ts +2 -0
  187. package/dist/server/resume/index.js +2 -0
  188. package/dist/server/resume/routes.d.ts +41 -0
  189. package/dist/server/resume/routes.js +50 -0
  190. package/dist/server/resume/store.d.ts +37 -0
  191. package/dist/server/resume/store.js +38 -0
  192. package/dist/server/retention/archive.d.ts +78 -0
  193. package/dist/server/retention/archive.js +284 -0
  194. package/dist/server/retention/fleet.d.ts +50 -0
  195. package/dist/server/retention/fleet.js +66 -0
  196. package/dist/server/retention/index.d.ts +38 -0
  197. package/dist/server/retention/index.js +38 -0
  198. package/dist/server/retention/policy.d.ts +263 -0
  199. package/dist/server/retention/policy.js +264 -0
  200. package/dist/server/retention/store.d.ts +101 -0
  201. package/dist/server/retention/store.js +251 -0
  202. package/dist/server/retention/sweeper.d.ts +69 -0
  203. package/dist/server/retention/sweeper.js +289 -0
  204. package/dist/server/retention/target.d.ts +143 -0
  205. package/dist/server/retention/target.js +438 -0
  206. package/dist/server/search/index.d.ts +2 -0
  207. package/dist/server/search/index.js +6 -0
  208. package/dist/server/search/pgSearchProvider.d.ts +91 -0
  209. package/dist/server/search/pgSearchProvider.js +330 -0
  210. package/dist/server/search/pgSearchProvider.pg.test.d.ts +1 -0
  211. package/dist/server/search/pgSearchProvider.pg.test.js +338 -0
  212. package/dist/server/search/pgSearchSchema.d.ts +81 -0
  213. package/dist/server/search/pgSearchSchema.js +89 -0
  214. package/dist/server/sharing/index.d.ts +18 -0
  215. package/dist/server/sharing/index.js +16 -0
  216. package/dist/server/sharing/migrations.d.ts +6 -0
  217. package/dist/server/sharing/migrations.js +79 -0
  218. package/dist/server/sharing/router.d.ts +111 -0
  219. package/dist/server/sharing/router.js +93 -0
  220. package/dist/server/sharing/sql.d.ts +67 -0
  221. package/dist/server/sharing/sql.js +88 -0
  222. package/dist/server/sharing/store.d.ts +173 -0
  223. package/dist/server/sharing/store.js +484 -0
  224. package/dist/server/sqlite/backup.d.ts +48 -0
  225. package/dist/server/sqlite/backup.js +97 -0
  226. package/dist/server/sqlite/browse.d.ts +42 -0
  227. package/dist/server/sqlite/browse.js +84 -0
  228. package/dist/server/sqlite/export.d.ts +32 -0
  229. package/dist/server/sqlite/export.js +42 -0
  230. package/dist/server/sqlite/fts5.d.ts +106 -0
  231. package/dist/server/sqlite/fts5.js +154 -0
  232. package/dist/server/sqlite/health.d.ts +44 -0
  233. package/dist/server/sqlite/health.js +52 -0
  234. package/dist/server/sqlite/import.d.ts +36 -0
  235. package/dist/server/sqlite/import.js +108 -0
  236. package/dist/server/sqlite/index.d.ts +9 -0
  237. package/dist/server/sqlite/index.js +15 -0
  238. package/dist/server/sqlite/readonlySql.d.ts +15 -0
  239. package/dist/server/sqlite/readonlySql.js +65 -0
  240. package/dist/server/sqlite/stats.d.ts +22 -0
  241. package/dist/server/sqlite/stats.js +32 -0
  242. package/dist/server/sqlite/viewer.d.ts +17 -0
  243. package/dist/server/sqlite/viewer.js +56 -0
  244. package/dist/server/storage/binaryServerProcessing.d.ts +23 -0
  245. package/dist/server/storage/binaryServerProcessing.js +81 -0
  246. package/dist/server/storage/binaryServerStore.d.ts +3 -0
  247. package/dist/server/storage/binaryServerStore.js +95 -0
  248. package/dist/server/storage/diskStore.d.ts +2 -0
  249. package/dist/server/storage/diskStore.js +88 -0
  250. package/dist/server/storage/files/adapter.d.ts +33 -0
  251. package/dist/server/storage/files/adapter.js +203 -0
  252. package/dist/server/storage/files/catalogue.d.ts +72 -0
  253. package/dist/server/storage/files/catalogue.js +1033 -0
  254. package/dist/server/storage/files/chunkedUpload.d.ts +56 -0
  255. package/dist/server/storage/files/chunkedUpload.js +70 -0
  256. package/dist/server/storage/files/fileRoutes.d.ts +51 -0
  257. package/dist/server/storage/files/fileRoutes.js +405 -0
  258. package/dist/server/storage/files/index.d.ts +9 -0
  259. package/dist/server/storage/files/index.js +11 -0
  260. package/dist/server/storage/files/migrations.d.ts +2 -0
  261. package/dist/server/storage/files/migrations.js +163 -0
  262. package/dist/server/storage/files/mimeByExt.d.ts +1 -0
  263. package/dist/server/storage/files/mimeByExt.js +13 -0
  264. package/dist/server/storage/files/stack.d.ts +136 -0
  265. package/dist/server/storage/files/stack.js +132 -0
  266. package/dist/server/storage/files/storageOps.d.ts +54 -0
  267. package/dist/server/storage/files/storageOps.js +110 -0
  268. package/dist/server/storage/files/types.d.ts +261 -0
  269. package/dist/server/storage/files/types.js +22 -0
  270. package/dist/server/storage/imageThumbnail.d.ts +3 -0
  271. package/dist/server/storage/imageThumbnail.js +50 -0
  272. package/dist/server/storage/index.d.ts +15 -0
  273. package/dist/server/storage/index.js +27 -0
  274. package/dist/server/storage/mountMediaRoutes.d.ts +28 -0
  275. package/dist/server/storage/mountMediaRoutes.js +141 -0
  276. package/dist/server/storage/objectStoreMediaStore.d.ts +4 -0
  277. package/dist/server/storage/objectStoreMediaStore.js +57 -0
  278. package/dist/server/storage/s3ObjectStore.d.ts +63 -0
  279. package/dist/server/storage/s3ObjectStore.js +187 -0
  280. package/dist/server/storage/sources.d.ts +47 -0
  281. package/dist/server/storage/sources.js +148 -0
  282. package/dist/server/storage/streamRange.d.ts +19 -0
  283. package/dist/server/storage/streamRange.js +69 -0
  284. package/dist/server/storage/streamToken.d.ts +16 -0
  285. package/dist/server/storage/streamToken.js +54 -0
  286. package/dist/server/storage/transcodeJobs.d.ts +65 -0
  287. package/dist/server/storage/transcodeJobs.js +104 -0
  288. package/dist/server/storage/types.d.ts +193 -0
  289. package/dist/server/storage/types.js +41 -0
  290. package/dist/server/storage/videoDerivativeKeys.d.ts +127 -0
  291. package/dist/server/storage/videoDerivativeKeys.js +149 -0
  292. package/dist/server/storage/videoProcessor.d.ts +30 -0
  293. package/dist/server/storage/videoProcessor.js +148 -0
  294. package/dist/server/sync/alarm.d.ts +34 -0
  295. package/dist/server/sync/alarm.js +91 -0
  296. package/dist/server/sync/commands.d.ts +87 -0
  297. package/dist/server/sync/commands.js +241 -0
  298. package/dist/server/sync/engine.d.ts +62 -0
  299. package/dist/server/sync/engine.js +184 -0
  300. package/dist/server/sync/http.d.ts +106 -0
  301. package/dist/server/sync/http.js +243 -0
  302. package/dist/server/sync/index.d.ts +41 -0
  303. package/dist/server/sync/index.js +41 -0
  304. package/dist/server/sync/opLog.d.ts +61 -0
  305. package/dist/server/sync/opLog.js +96 -0
  306. package/dist/server/sync/planner.d.ts +31 -0
  307. package/dist/server/sync/planner.js +30 -0
  308. package/dist/server/sync/status.d.ts +63 -0
  309. package/dist/server/sync/status.js +49 -0
  310. package/dist/server/sync/timer.d.ts +52 -0
  311. package/dist/server/sync/timer.js +170 -0
  312. package/dist/server/sync/tokens.d.ts +25 -0
  313. package/dist/server/sync/tokens.js +51 -0
  314. package/dist/server/sync/types.d.ts +101 -0
  315. package/dist/server/sync/types.js +29 -0
  316. package/dist/server/ws/eventBus.d.ts +14 -0
  317. package/dist/server/ws/eventBus.js +42 -0
  318. package/dist/server/ws/hub.d.ts +20 -0
  319. package/dist/server/ws/hub.js +154 -0
  320. package/dist/server/ws/index.d.ts +5 -0
  321. package/dist/server/ws/index.js +7 -0
  322. package/dist/server/ws/presence.d.ts +11 -0
  323. package/dist/server/ws/presence.js +20 -0
  324. package/dist/server/ws/room.d.ts +17 -0
  325. package/dist/server/ws/room.js +23 -0
  326. package/dist/server/ws/types.d.ts +122 -0
  327. package/dist/server/ws/types.js +1 -0
  328. package/package.json +234 -0
  329. package/src/declaredDepsAreImported.spec.ts +87 -0
  330. package/src/leafSubpathsImportNothing.spec.ts +358 -0
  331. package/src/lockfileIsTracked.spec.ts +50 -0
  332. package/src/namedSubpathsResolve.spec.ts +122 -0
  333. package/src/noPathDeps.spec.ts +24 -0
  334. package/src/publishShape.spec.ts +89 -0
  335. package/src/server/activity/index.ts +59 -0
  336. package/src/server/activity/migrations.ts +67 -0
  337. package/src/server/activity/shareSource.ts +57 -0
  338. package/src/server/activity/store.spec.ts +255 -0
  339. package/src/server/activity/store.ts +368 -0
  340. package/src/server/app.spec.ts +243 -0
  341. package/src/server/app.ts +89 -0
  342. package/src/server/audit/index.ts +2 -0
  343. package/src/server/audit/migrations.ts +88 -0
  344. package/src/server/audit/sqliteSink.test.ts +200 -0
  345. package/src/server/audit/sqliteSink.ts +162 -0
  346. package/src/server/auth/cookie.spec.ts +46 -0
  347. package/src/server/auth/cookie.ts +42 -0
  348. package/src/server/auth/csrf.spec.ts +81 -0
  349. package/src/server/auth/csrf.ts +83 -0
  350. package/src/server/auth/fileTokenSigner.ts +40 -0
  351. package/src/server/auth/getOnly.spec.ts +53 -0
  352. package/src/server/auth/getOnly.ts +32 -0
  353. package/src/server/auth/index.ts +51 -0
  354. package/src/server/auth/jwt.spec.ts +106 -0
  355. package/src/server/auth/jwt.ts +75 -0
  356. package/src/server/auth/loginThrottle.spec.ts +213 -0
  357. package/src/server/auth/loginThrottle.ts +261 -0
  358. package/src/server/auth/middleware.spec.ts +148 -0
  359. package/src/server/auth/middleware.ts +56 -0
  360. package/src/server/auth/password.spec.ts +48 -0
  361. package/src/server/auth/password.ts +71 -0
  362. package/src/server/auth/sessionStore.spec.ts +189 -0
  363. package/src/server/auth/sessionStore.ts +261 -0
  364. package/src/server/auth/totp.spec.ts +73 -0
  365. package/src/server/auth/totp.ts +92 -0
  366. package/src/server/auth/types.ts +146 -0
  367. package/src/server/auth/vault.spec.ts +51 -0
  368. package/src/server/auth/vault.ts +71 -0
  369. package/src/server/context.ts +37 -0
  370. package/src/server/ctgr.spec.ts +138 -0
  371. package/src/server/ctgr.ts +108 -0
  372. package/src/server/db/kysely.spec.ts +62 -0
  373. package/src/server/db/kysely.ts +21 -0
  374. package/src/server/db/plumbingTypes.ts +126 -0
  375. package/src/server/dropzone/duplicates.ts +121 -0
  376. package/src/server/dropzone/index.ts +30 -0
  377. package/src/server/dropzone/scan.spec.ts +255 -0
  378. package/src/server/dropzone/scan.ts +342 -0
  379. package/src/server/errors.spec.ts +76 -0
  380. package/src/server/errors.ts +22 -0
  381. package/src/server/guard/argon2Binding.spec.ts +117 -0
  382. package/src/server/guard/argon2Binding.ts +195 -0
  383. package/src/server/guard/argon2Kat.spec.ts +250 -0
  384. package/src/server/guard/guard.spec.ts +390 -0
  385. package/src/server/guard/index.ts +6 -0
  386. package/src/server/guard/kdfWorker.spec.ts +283 -0
  387. package/src/server/guard/kdfWorker.ts +367 -0
  388. package/src/server/guard/kdfWorkerEntry.ts +16 -0
  389. package/src/server/guard/passwordVerifier.spec.ts +71 -0
  390. package/src/server/guard/passwordVerifier.ts +175 -0
  391. package/src/server/guard/revocationStore.spec.ts +135 -0
  392. package/src/server/guard/revocationStore.ts +291 -0
  393. package/src/server/guard/sessionSigner.ts +425 -0
  394. package/src/server/guard/verifier.ts +27 -0
  395. package/src/server/incidents/channels.ts +487 -0
  396. package/src/server/incidents/checks.ts +120 -0
  397. package/src/server/incidents/incidents.spec.ts +463 -0
  398. package/src/server/incidents/index.ts +96 -0
  399. package/src/server/incidents/migrations.ts +66 -0
  400. package/src/server/incidents/notifier.ts +215 -0
  401. package/src/server/incidents/reporter.ts +110 -0
  402. package/src/server/incidents/routes.ts +314 -0
  403. package/src/server/incidents/store.ts +329 -0
  404. package/src/server/incidents/types.ts +116 -0
  405. package/src/server/index.ts +248 -0
  406. package/src/server/jobs/fireQueue.spec.ts +85 -0
  407. package/src/server/jobs/fireQueue.ts +37 -0
  408. package/src/server/jobs/index.ts +26 -0
  409. package/src/server/jobs/logger.ts +16 -0
  410. package/src/server/jobs/plainjobAdapter.spec.ts +34 -0
  411. package/src/server/jobs/plainjobAdapter.ts +54 -0
  412. package/src/server/jobs/polling.spec.ts +219 -0
  413. package/src/server/jobs/polling.ts +81 -0
  414. package/src/server/jobs/runner.spec.ts +213 -0
  415. package/src/server/jobs/runner.ts +213 -0
  416. package/src/server/jobs/timeout.spec.ts +35 -0
  417. package/src/server/jobs/timeout.ts +53 -0
  418. package/src/server/jobs/types.ts +188 -0
  419. package/src/server/master-lock/guard.spec.ts +347 -0
  420. package/src/server/master-lock/guard.ts +290 -0
  421. package/src/server/master-lock/index.ts +44 -0
  422. package/src/server/master-lock/lockPage.spec.ts +88 -0
  423. package/src/server/master-lock/lockPage.ts +313 -0
  424. package/src/server/master-lock/masterLock.spec.ts +338 -0
  425. package/src/server/master-lock/masterLock.ts +627 -0
  426. package/src/server/master-lock/principals.spec.ts +378 -0
  427. package/src/server/master-lock/principals.ts +175 -0
  428. package/src/server/master-lock/seed.spec.ts +69 -0
  429. package/src/server/master-lock/seed.ts +112 -0
  430. package/src/server/master-lock/store.ts +35 -0
  431. package/src/server/media-bun/image.spec.ts +115 -0
  432. package/src/server/media-bun/image.ts +134 -0
  433. package/src/server/media-bun/index.ts +52 -0
  434. package/src/server/media-bun/streaming.spec.ts +134 -0
  435. package/src/server/media-bun/streaming.ts +347 -0
  436. package/src/server/media-bun/video.spec.ts +115 -0
  437. package/src/server/media-bun/video.ts +232 -0
  438. package/src/server/metrics/metricsBuffer.ts +92 -0
  439. package/src/server/metrics/normalizeRoute.spec.ts +51 -0
  440. package/src/server/metrics/normalizeRoute.ts +63 -0
  441. package/src/server/middleware/correlationId.ts +24 -0
  442. package/src/server/middleware/corsAllowlist.spec.ts +118 -0
  443. package/src/server/middleware/corsAllowlist.ts +69 -0
  444. package/src/server/middleware/errorEnvelope.ts +38 -0
  445. package/src/server/middleware/index.ts +19 -0
  446. package/src/server/middleware/localOrigin.spec.ts +168 -0
  447. package/src/server/middleware/localOrigin.ts +139 -0
  448. package/src/server/middleware/rateLimit.spec.ts +72 -0
  449. package/src/server/middleware/rateLimit.ts +125 -0
  450. package/src/server/middleware/requestLogger.ts +95 -0
  451. package/src/server/middleware/securityHeaders.ts +78 -0
  452. package/src/server/middleware/zValidator.spec.ts +251 -0
  453. package/src/server/middleware/zValidator.ts +114 -0
  454. package/src/server/migration/runner.spec.ts +88 -0
  455. package/src/server/migration/runner.ts +87 -0
  456. package/src/server/migration/types.ts +17 -0
  457. package/src/server/net/outboundGuard.spec.ts +90 -0
  458. package/src/server/net/outboundGuard.ts +205 -0
  459. package/src/server/notifications/hub.ts +245 -0
  460. package/src/server/notifications/index.ts +77 -0
  461. package/src/server/notifications/migrations.ts +39 -0
  462. package/src/server/notifications/routes.ts +177 -0
  463. package/src/server/notifications/service.spec.ts +305 -0
  464. package/src/server/notifications/service.ts +112 -0
  465. package/src/server/paths/appDataHome.spec.ts +87 -0
  466. package/src/server/paths/appDataHome.ts +84 -0
  467. package/src/server/private-media/blobRoute.spec.ts +165 -0
  468. package/src/server/private-media/crypto.spec.ts +81 -0
  469. package/src/server/private-media/crypto.ts +155 -0
  470. package/src/server/private-media/gatePolicy.spec.ts +98 -0
  471. package/src/server/private-media/index-store.ts +83 -0
  472. package/src/server/private-media/mountPrivateRoutes.ts +265 -0
  473. package/src/server/private-media/pipeline.spec.ts +111 -0
  474. package/src/server/private-media/pipeline.ts +170 -0
  475. package/src/server/private-media/routes.spec.ts +265 -0
  476. package/src/server/private-media/unlock.ts +112 -0
  477. package/src/server/resume/index.ts +8 -0
  478. package/src/server/resume/resume.spec.ts +118 -0
  479. package/src/server/resume/routes.ts +92 -0
  480. package/src/server/resume/store.ts +82 -0
  481. package/src/server/retention/archive.spec.ts +181 -0
  482. package/src/server/retention/archive.ts +353 -0
  483. package/src/server/retention/fleet.ts +104 -0
  484. package/src/server/retention/index.ts +101 -0
  485. package/src/server/retention/policy.spec.ts +132 -0
  486. package/src/server/retention/policy.ts +392 -0
  487. package/src/server/retention/store.spec.ts +236 -0
  488. package/src/server/retention/store.ts +338 -0
  489. package/src/server/retention/sweeper.spec.ts +321 -0
  490. package/src/server/retention/sweeper.ts +367 -0
  491. package/src/server/retention/target.spec.ts +592 -0
  492. package/src/server/retention/target.ts +613 -0
  493. package/src/server/search/index.ts +12 -0
  494. package/src/server/search/pgSearchProvider.pg.test.ts +392 -0
  495. package/src/server/search/pgSearchProvider.ts +421 -0
  496. package/src/server/search/pgSearchSchema.ts +147 -0
  497. package/src/server/sharing/index.ts +65 -0
  498. package/src/server/sharing/migrations.ts +128 -0
  499. package/src/server/sharing/router.spec.ts +167 -0
  500. package/src/server/sharing/router.ts +194 -0
  501. package/src/server/sharing/sql.ts +109 -0
  502. package/src/server/sharing/store.spec.ts +408 -0
  503. package/src/server/sharing/store.ts +737 -0
  504. package/src/server/sqlite/backup.spec.ts +114 -0
  505. package/src/server/sqlite/backup.ts +154 -0
  506. package/src/server/sqlite/browse.spec.ts +70 -0
  507. package/src/server/sqlite/browse.ts +120 -0
  508. package/src/server/sqlite/export.spec.ts +43 -0
  509. package/src/server/sqlite/export.ts +74 -0
  510. package/src/server/sqlite/fts5.spec.ts +175 -0
  511. package/src/server/sqlite/fts5.ts +255 -0
  512. package/src/server/sqlite/health.spec.ts +79 -0
  513. package/src/server/sqlite/health.ts +105 -0
  514. package/src/server/sqlite/import.spec.ts +75 -0
  515. package/src/server/sqlite/import.ts +141 -0
  516. package/src/server/sqlite/index.ts +64 -0
  517. package/src/server/sqlite/readonlySql.spec.ts +58 -0
  518. package/src/server/sqlite/readonlySql.ts +67 -0
  519. package/src/server/sqlite/stats.spec.ts +21 -0
  520. package/src/server/sqlite/stats.ts +61 -0
  521. package/src/server/sqlite/viewer.spec.ts +127 -0
  522. package/src/server/sqlite/viewer.ts +72 -0
  523. package/src/server/storage/binaryServerProcessing.integration.spec.ts +222 -0
  524. package/src/server/storage/binaryServerProcessing.spec.ts +150 -0
  525. package/src/server/storage/binaryServerProcessing.ts +127 -0
  526. package/src/server/storage/binaryServerStore.ts +102 -0
  527. package/src/server/storage/chunked.integration.spec.ts +185 -0
  528. package/src/server/storage/diskStore.ts +83 -0
  529. package/src/server/storage/files/adapter.photo.display.spec.ts +169 -0
  530. package/src/server/storage/files/adapter.photo.spec.ts +128 -0
  531. package/src/server/storage/files/adapter.spec.ts +121 -0
  532. package/src/server/storage/files/adapter.ts +255 -0
  533. package/src/server/storage/files/catalogue.media-host.spec.ts +184 -0
  534. package/src/server/storage/files/catalogue.orderings.spec.ts +157 -0
  535. package/src/server/storage/files/catalogue.private.spec.ts +106 -0
  536. package/src/server/storage/files/catalogue.recover.spec.ts +131 -0
  537. package/src/server/storage/files/catalogue.sort.spec.ts +154 -0
  538. package/src/server/storage/files/catalogue.spec.ts +133 -0
  539. package/src/server/storage/files/catalogue.trash-import.spec.ts +125 -0
  540. package/src/server/storage/files/catalogue.ts +1326 -0
  541. package/src/server/storage/files/chunkedUpload.spec.ts +111 -0
  542. package/src/server/storage/files/chunkedUpload.ts +146 -0
  543. package/src/server/storage/files/collections.spec.ts +294 -0
  544. package/src/server/storage/files/fileRoutes.replace.spec.ts +88 -0
  545. package/src/server/storage/files/fileRoutes.spec.ts +327 -0
  546. package/src/server/storage/files/fileRoutes.ts +462 -0
  547. package/src/server/storage/files/index.ts +66 -0
  548. package/src/server/storage/files/migrations.ts +187 -0
  549. package/src/server/storage/files/mimeByExt.ts +14 -0
  550. package/src/server/storage/files/stack.spec.ts +258 -0
  551. package/src/server/storage/files/stack.ts +284 -0
  552. package/src/server/storage/files/storageOps.spec.ts +126 -0
  553. package/src/server/storage/files/storageOps.ts +213 -0
  554. package/src/server/storage/files/types.ts +285 -0
  555. package/src/server/storage/imageThumbnail.spec.ts +81 -0
  556. package/src/server/storage/imageThumbnail.ts +60 -0
  557. package/src/server/storage/index.ts +122 -0
  558. package/src/server/storage/mountMediaRoutes.ts +231 -0
  559. package/src/server/storage/objectStore.spec.ts +272 -0
  560. package/src/server/storage/objectStoreMediaStore.ts +63 -0
  561. package/src/server/storage/s3ObjectStore.ts +239 -0
  562. package/src/server/storage/sources.ts +245 -0
  563. package/src/server/storage/storage.spec.ts +219 -0
  564. package/src/server/storage/streamRange.ts +92 -0
  565. package/src/server/storage/streamToken.ts +76 -0
  566. package/src/server/storage/transcode.integration.spec.ts +134 -0
  567. package/src/server/storage/transcodeJobs.spec.ts +152 -0
  568. package/src/server/storage/transcodeJobs.ts +171 -0
  569. package/src/server/storage/types.ts +230 -0
  570. package/src/server/storage/videoDerivativeKeys.spec.ts +112 -0
  571. package/src/server/storage/videoDerivativeKeys.ts +172 -0
  572. package/src/server/storage/videoProcessor.posterOnly.spec.ts +106 -0
  573. package/src/server/storage/videoProcessor.ts +229 -0
  574. package/src/server/sync/alarm.spec.ts +149 -0
  575. package/src/server/sync/alarm.ts +137 -0
  576. package/src/server/sync/commands.spec.ts +145 -0
  577. package/src/server/sync/commands.ts +361 -0
  578. package/src/server/sync/engine.spec.ts +496 -0
  579. package/src/server/sync/engine.ts +255 -0
  580. package/src/server/sync/http.ts +316 -0
  581. package/src/server/sync/httpRemote.spec.ts +158 -0
  582. package/src/server/sync/index.ts +90 -0
  583. package/src/server/sync/opLog.spec.ts +110 -0
  584. package/src/server/sync/opLog.ts +189 -0
  585. package/src/server/sync/planner.spec.ts +53 -0
  586. package/src/server/sync/planner.ts +62 -0
  587. package/src/server/sync/status.spec.ts +92 -0
  588. package/src/server/sync/status.ts +108 -0
  589. package/src/server/sync/timer.spec.ts +150 -0
  590. package/src/server/sync/timer.ts +207 -0
  591. package/src/server/sync/tokens.spec.ts +38 -0
  592. package/src/server/sync/tokens.ts +94 -0
  593. package/src/server/sync/types.ts +108 -0
  594. package/src/server/ws/eventBus.spec.ts +67 -0
  595. package/src/server/ws/eventBus.ts +46 -0
  596. package/src/server/ws/hub.integration.spec.ts +93 -0
  597. package/src/server/ws/hub.spec.ts +282 -0
  598. package/src/server/ws/hub.ts +168 -0
  599. package/src/server/ws/index.ts +20 -0
  600. package/src/server/ws/presence.spec.ts +27 -0
  601. package/src/server/ws/presence.ts +26 -0
  602. package/src/server/ws/room.spec.ts +36 -0
  603. package/src/server/ws/room.ts +29 -0
  604. package/src/server/ws/types.ts +134 -0
  605. package/src/skippedTestsAreAnswered.spec.ts +215 -0
  606. package/src/testTmpRoot.spec.ts +102 -0
@@ -0,0 +1,85 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
3
+ import { createFireQueue } from './fireQueue';
4
+
5
+ const JOB_STATE_DDL = `CREATE TABLE job_state (name TEXT PRIMARY KEY, last_run_at TEXT, meta TEXT)`;
6
+ const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
7
+ const quietLogger = { error() {}, warn() {}, info() {}, debug() {} };
8
+
9
+ describe('createFireQueue', () => {
10
+ let db: Database;
11
+ beforeEach(() => {
12
+ db = new Database(':memory:');
13
+ db.run(JOB_STATE_DDL);
14
+ });
15
+ afterEach(() => db.close());
16
+
17
+ test('delivers each due item (acceptance)', async () => {
18
+ const items: string[] = [];
19
+ const queue = createFireQueue<string>({
20
+ name: 'test-q',
21
+ db,
22
+ intervalMs: 30,
23
+ getDue: () => ['item-1'],
24
+ async deliver(item) {
25
+ items.push(item);
26
+ },
27
+ markSent: () => {},
28
+ });
29
+ queue.start();
30
+ await sleep(50);
31
+ queue.stop();
32
+ expect(items).toContain('item-1');
33
+ });
34
+
35
+ test('a delivery failure routes to onError and the loop continues (integration)', async () => {
36
+ const delivered: string[] = [];
37
+ const errored: string[] = [];
38
+ let drained = false;
39
+ const queue = createFireQueue<string>({
40
+ name: 'resilient-q',
41
+ db,
42
+ intervalMs: 1000, // single boot fire is enough
43
+ logger: quietLogger,
44
+ getDue: () => (drained ? [] : ['bad', 'good']),
45
+ async deliver(item) {
46
+ if (item === 'bad') throw new Error('delivery boom');
47
+ delivered.push(item);
48
+ },
49
+ markSent: () => {
50
+ drained = true;
51
+ },
52
+ onError(item) {
53
+ errored.push(item);
54
+ },
55
+ });
56
+ queue.start();
57
+ await sleep(40);
58
+ queue.stop();
59
+ expect(errored).toEqual(['bad']);
60
+ expect(delivered).toEqual(['good']); // continued past the failure
61
+ });
62
+
63
+ test('markSent is only called after a successful deliver', async () => {
64
+ const sent: string[] = [];
65
+ let drained = false;
66
+ const queue = createFireQueue<string>({
67
+ name: 'mark-q',
68
+ db,
69
+ intervalMs: 1000,
70
+ logger: quietLogger,
71
+ getDue: () => (drained ? [] : ['a', 'fail', 'b']),
72
+ async deliver(item) {
73
+ if (item === 'fail') throw new Error('nope');
74
+ },
75
+ markSent: (item) => {
76
+ sent.push(item);
77
+ if (item === 'b') drained = true;
78
+ },
79
+ });
80
+ queue.start();
81
+ await sleep(40);
82
+ queue.stop();
83
+ expect(sent).toEqual(['a', 'b']); // 'fail' never marked sent
84
+ });
85
+ });
@@ -0,0 +1,37 @@
1
+ import { defaultJobLogger } from './logger';
2
+ import { createPollingJob } from './polling';
3
+ import { DEFAULT_FIRE_INTERVAL_MS, type FireQueue, type FireQueueOpts } from './types';
4
+
5
+ /**
6
+ * The "select due rows → deliver each → mark sent" pattern, as a {@link PollingJob}.
7
+ * This is ca's `event_reminders` / `alarms` delivery loop generalized: each poll
8
+ * pulls the due rows, delivers them one at a time, and marks each sent only after a
9
+ * successful delivery. A failed `deliver` routes to `onError` and is logged, and
10
+ * the loop CONTINUES with the next item — one bad row never stalls the queue.
11
+ *
12
+ * Because it fires once on boot (default), any rows that came due while the process
13
+ * was down are delivered on the first poll after restart.
14
+ */
15
+ export function createFireQueue<T>(opts: FireQueueOpts<T>): FireQueue {
16
+ const logger = opts.logger ?? defaultJobLogger;
17
+
18
+ return createPollingJob({
19
+ name: opts.name,
20
+ db: opts.db,
21
+ intervalMs: opts.intervalMs ?? DEFAULT_FIRE_INTERVAL_MS,
22
+ runOnBoot: opts.runOnBoot,
23
+ logger,
24
+ async run({ db, signal }) {
25
+ for (const item of opts.getDue(db)) {
26
+ if (signal.aborted) break;
27
+ try {
28
+ await opts.deliver(item, db);
29
+ opts.markSent(item, db);
30
+ } catch (err) {
31
+ opts.onError?.(item, err);
32
+ logger.error(`[fireQueue] ${opts.name} delivery failed`, err);
33
+ }
34
+ }
35
+ },
36
+ });
37
+ }
@@ -0,0 +1,26 @@
1
+ // cursedbelt jobs & scheduler — the in-process scheduled-work engine shared by
2
+ // ca + ru. cursedbelt owns the mechanism (durable cron scheduling via plainjob,
3
+ // restart-aware pollers, the due-row fire queue, run auditing); app job
4
+ // *definitions* stay business and consume these primitives.
5
+
6
+ export { createFireQueue } from './fireQueue';
7
+ export { defaultJobLogger } from './logger';
8
+ export { createPollingJob } from './polling';
9
+ export { createJobRunner } from './runner';
10
+ export { JobTimeoutError } from './timeout';
11
+ export {
12
+ type CreateJobRunnerOptions,
13
+ DEFAULT_FIRE_INTERVAL_MS,
14
+ DEFAULT_JOB_RETENTION,
15
+ DEFAULT_JOB_TIMEOUT_MS,
16
+ type FireQueue,
17
+ type FireQueueOpts,
18
+ type JobContext,
19
+ type JobDef,
20
+ type JobLogger,
21
+ type JobRetentionOptions,
22
+ type JobRunner,
23
+ type JobRunRecord,
24
+ type PollingJob,
25
+ type PollingJobOpts,
26
+ } from './types';
@@ -0,0 +1,16 @@
1
+ import type { JobLogger } from './types';
2
+
3
+ /**
4
+ * The default job logger: warn/info/error go to the console, `debug` is a no-op.
5
+ *
6
+ * The underlying plainjob worker logs at `debug` on *every* poll ("checking for
7
+ * scheduled jobs", once per second by default), so a console `debug` would spam.
8
+ * Silencing it here keeps the default quiet while still surfacing real problems.
9
+ * Inject your own {@link JobLogger} (e.g. the app logger) to capture debug.
10
+ */
11
+ export const defaultJobLogger: JobLogger = {
12
+ error: (message, ...meta) => console.error(message, ...meta),
13
+ warn: (message, ...meta) => console.warn(message, ...meta),
14
+ info: (message, ...meta) => console.info(message, ...meta),
15
+ debug: () => {},
16
+ };
@@ -0,0 +1,34 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
3
+ import { createPlainjobConnection } from './plainjobAdapter';
4
+
5
+ describe('createPlainjobConnection', () => {
6
+ let db: Database;
7
+ beforeEach(() => {
8
+ db = new Database(':memory:');
9
+ db.run('CREATE TABLE t (type TEXT NOT NULL, n INTEGER)');
10
+ });
11
+ afterEach(() => db.close());
12
+
13
+ test('binds bare-key params that bun:sqlite would otherwise reject (the plainjob fix)', () => {
14
+ const conn = createPlainjobConnection(db);
15
+ const insert = conn.prepare('INSERT INTO t (type, n) VALUES (@type, @n)');
16
+ // plainjob passes bare keys; on Bun ≥ 1.3 these bind NULL without the adapter.
17
+ insert.run({ type: 'a', n: 1 });
18
+ const row = db.query('SELECT type, n FROM t').get() as { type: string; n: number };
19
+ expect(row).toEqual({ type: 'a', n: 1 });
20
+ });
21
+
22
+ test('leaves already-sigilled keys and positional params untouched', () => {
23
+ const conn = createPlainjobConnection(db);
24
+ conn.prepare('INSERT INTO t (type, n) VALUES (@type, @n)').run({ '@type': 'b', '@n': 2 });
25
+ const got = conn.prepare('SELECT n FROM t WHERE type = ?').get('b') as { n: number };
26
+ expect(got.n).toBe(2);
27
+ });
28
+
29
+ test('close() is a no-op so the shared db stays open', () => {
30
+ const conn = createPlainjobConnection(db);
31
+ conn.close();
32
+ expect(() => db.query('SELECT 1 AS x').get()).not.toThrow();
33
+ });
34
+ });
@@ -0,0 +1,54 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import type { Connection } from 'plainjob';
3
+
4
+ /**
5
+ * A plainjob {@link Connection} over a `bun:sqlite` Database, with two deliberate
6
+ * deviations from plainjob's own `bun()` adapter:
7
+ *
8
+ * 1. **Named-parameter binding fix.** plainjob binds its prepared statements with
9
+ * *bare* object keys (`{ type, cronExpression }`). On Bun ≥ 1.3 `bun:sqlite`
10
+ * requires the sigil — bare keys silently bind NULL (→ `NOT NULL constraint
11
+ * failed`). This adapter rewrites each object key to `@key` (leaving keys that
12
+ * already carry an `@ $ :` sigil, and non-object/positional params, untouched),
13
+ * so plainjob runs unmodified on current Bun. Remove once plainjob ships a fix.
14
+ *
15
+ * 2. **`close()` is a no-op.** plainjob's queue and the app share ONE connection;
16
+ * `queue.close()` would otherwise close the app's database out from under it.
17
+ * Closing is the app's job — the runner only stops the queue's maintenance
18
+ * timer (which `queue.close()` still does). The DB outlives the runner.
19
+ */
20
+ export function createPlainjobConnection(database: Database): Connection {
21
+ const prefixKeys = (params: unknown[]): unknown[] =>
22
+ params.map((param) => {
23
+ if (!param || typeof param !== 'object' || Array.isArray(param)) return param;
24
+ return Object.fromEntries(
25
+ Object.entries(param as Record<string, unknown>).map(([key, value]) => [
26
+ /^[@$:]/.test(key) ? key : `@${key}`,
27
+ value,
28
+ ]),
29
+ );
30
+ });
31
+
32
+ return {
33
+ driver: 'bun:sqlite',
34
+ filename: database.filename,
35
+ pragma: (stmt: string) => {
36
+ database.exec(`PRAGMA ${stmt};`);
37
+ },
38
+ exec: (stmt: string) => {
39
+ database.exec(stmt);
40
+ },
41
+ transaction: database.transaction.bind(database) as Connection['transaction'],
42
+ prepare: (sql: string) => {
43
+ const stmt = database.query(sql);
44
+ return {
45
+ run: (...params: unknown[]) => stmt.run(...(prefixKeys(params) as never[])),
46
+ get: (...params: unknown[]) => stmt.get(...(prefixKeys(params) as never[])),
47
+ all: (...params: unknown[]) => stmt.all(...(prefixKeys(params) as never[])),
48
+ };
49
+ },
50
+ close: () => {
51
+ // Intentionally empty — the app owns the shared DB's lifecycle.
52
+ },
53
+ };
54
+ }
@@ -0,0 +1,219 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
3
+ import { createPollingJob } from './polling';
4
+
5
+ const JOB_STATE_DDL = `CREATE TABLE job_state (
6
+ name TEXT PRIMARY KEY,
7
+ last_run_at TEXT,
8
+ meta TEXT
9
+ )`;
10
+
11
+ const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
12
+ const lastRunAt = (db: Database, name: string) =>
13
+ (db.query('SELECT last_run_at FROM job_state WHERE name = ?').get(name) as
14
+ | { last_run_at: string | null }
15
+ | undefined) ?? undefined;
16
+
17
+ describe('createPollingJob', () => {
18
+ let db: Database;
19
+ beforeEach(() => {
20
+ db = new Database(':memory:');
21
+ db.run(JOB_STATE_DDL);
22
+ });
23
+ afterEach(() => db.close());
24
+
25
+ test('fires on boot and again on each interval (acceptance)', async () => {
26
+ let count = 0;
27
+ const job = createPollingJob({
28
+ name: 'test',
29
+ intervalMs: 50,
30
+ db,
31
+ runOnBoot: true,
32
+ async run() {
33
+ count++;
34
+ },
35
+ });
36
+ job.start();
37
+ await sleep(120);
38
+ job.stop();
39
+ expect(count).toBeGreaterThanOrEqual(2); // boot + at least one interval
40
+ });
41
+
42
+ test('runOnBoot:false skips the immediate fire', async () => {
43
+ let count = 0;
44
+ const job = createPollingJob({
45
+ name: 'no-boot',
46
+ intervalMs: 1000,
47
+ db,
48
+ runOnBoot: false,
49
+ async run() {
50
+ count++;
51
+ },
52
+ });
53
+ job.start();
54
+ await sleep(20);
55
+ job.stop();
56
+ expect(count).toBe(0);
57
+ });
58
+
59
+ test('records last_run_at on success', async () => {
60
+ const job = createPollingJob({
61
+ name: 'stamps',
62
+ intervalMs: 1000,
63
+ db,
64
+ async run() {},
65
+ });
66
+ job.start();
67
+ await sleep(20);
68
+ job.stop();
69
+ expect(lastRunAt(db, 'stamps')?.last_run_at).toBeTruthy();
70
+ });
71
+
72
+ test('a thrown handler is swallowed and does NOT advance last_run_at', async () => {
73
+ const job = createPollingJob({
74
+ name: 'boom',
75
+ intervalMs: 1000,
76
+ db,
77
+ logger: { error() {}, warn() {}, info() {}, debug() {} },
78
+ async run() {
79
+ throw new Error('handler failed');
80
+ },
81
+ });
82
+ job.start();
83
+ await sleep(20);
84
+ job.stop();
85
+ expect(lastRunAt(db, 'boom')).toBeUndefined();
86
+ });
87
+
88
+ test('preserves an existing job_state.meta on the last_run_at upsert', async () => {
89
+ db.run("INSERT INTO job_state (name, last_run_at, meta) VALUES ('keep', NULL, '{\"n\":1}')");
90
+ const job = createPollingJob({ name: 'keep', intervalMs: 1000, db, async run() {} });
91
+ job.start();
92
+ await sleep(20);
93
+ job.stop();
94
+ const row = db.query('SELECT last_run_at, meta FROM job_state WHERE name = ?').get('keep') as {
95
+ last_run_at: string | null;
96
+ meta: string | null;
97
+ };
98
+ expect(row.last_run_at).toBeTruthy();
99
+ expect(row.meta).toBe('{"n":1}');
100
+ });
101
+
102
+ test('ticks are non-reentrant — a slow run never overlaps itself', async () => {
103
+ let concurrent = 0;
104
+ let maxConcurrent = 0;
105
+ const job = createPollingJob({
106
+ name: 'slow',
107
+ intervalMs: 20,
108
+ db,
109
+ async run() {
110
+ concurrent++;
111
+ maxConcurrent = Math.max(maxConcurrent, concurrent);
112
+ await sleep(70);
113
+ concurrent--;
114
+ },
115
+ });
116
+ job.start();
117
+ await sleep(160);
118
+ job.stop();
119
+ await sleep(80); // let the final slow run unwind
120
+ expect(maxConcurrent).toBe(1);
121
+ });
122
+
123
+ test('stop() aborts the in-flight run signal', async () => {
124
+ let abortedDuringRun = false;
125
+ const job = createPollingJob({
126
+ name: 'abortable',
127
+ intervalMs: 1000,
128
+ db,
129
+ async run({ signal }) {
130
+ await new Promise<void>((resolve) => {
131
+ signal.addEventListener(
132
+ 'abort',
133
+ () => {
134
+ abortedDuringRun = true;
135
+ resolve();
136
+ },
137
+ { once: true },
138
+ );
139
+ });
140
+ },
141
+ });
142
+ job.start();
143
+ await sleep(20);
144
+ job.stop();
145
+ await sleep(20);
146
+ expect(abortedDuringRun).toBe(true);
147
+ });
148
+ });
149
+
150
+ /**
151
+ * The teardown race (2026-08-06, found while draining the orch board).
152
+ *
153
+ * `stop()` is what a host or a test calls immediately before closing the
154
+ * database. A run that was already awaiting `opts.run` then resumed and wrote
155
+ * `last_run_at` to a closed handle — "Database has closed", thrown out of a
156
+ * timer callback after the owning test had ended. It reddened
157
+ * cursed-satellites' orch-engine suite about one run in three with a stack
158
+ * pointing into this file and no relation to the test it failed.
159
+ */
160
+ describe("a run that is stopped mid-flight", () => {
161
+ test("does not stamp a completion", async () => {
162
+ const db = new Database(":memory:");
163
+ db.run(JOB_STATE_DDL);
164
+ let release: (() => void) | undefined;
165
+ const started = new Promise<void>((resolve) => {
166
+ release = resolve;
167
+ });
168
+ let running: (() => void) | undefined;
169
+ const inRun = new Promise<void>((resolve) => {
170
+ running = resolve;
171
+ });
172
+
173
+ const job = createPollingJob({
174
+ db,
175
+ name: "slow",
176
+ intervalMs: 60_000,
177
+ runOnBoot: true,
178
+ run: async () => {
179
+ running?.();
180
+ await started;
181
+ },
182
+ });
183
+ job.start();
184
+ await inRun;
185
+
186
+ // The exact order a teardown takes: stop, then close.
187
+ job.stop();
188
+ release?.();
189
+ await Promise.resolve();
190
+ await Promise.resolve();
191
+
192
+ // Nothing was stamped, because nothing completed on this job's terms.
193
+ const row = db.query("SELECT last_run_at FROM job_state WHERE name = ?").get("slow");
194
+ expect(row).toBeNull();
195
+ db.close();
196
+ });
197
+
198
+ test("a run that finishes BEFORE stop still stamps", async () => {
199
+ // The guard must not swallow the normal case.
200
+ const db = new Database(":memory:");
201
+ db.run(JOB_STATE_DDL);
202
+ const job = createPollingJob({
203
+ db,
204
+ name: "quick",
205
+ intervalMs: 60_000,
206
+ runOnBoot: true,
207
+ run: () => {},
208
+ });
209
+ job.start();
210
+ await Promise.resolve();
211
+ await Promise.resolve();
212
+ job.stop();
213
+ const row = db.query("SELECT last_run_at FROM job_state WHERE name = ?").get("quick") as
214
+ | { last_run_at: string }
215
+ | null;
216
+ expect(row?.last_run_at).toBeTruthy();
217
+ db.close();
218
+ });
219
+ });
@@ -0,0 +1,81 @@
1
+ import { defaultJobLogger } from './logger';
2
+ import type { JobContext, PollingJob, PollingJobOpts } from './types';
3
+
4
+ /**
5
+ * A periodic, restart-aware poller — the engine behind ca's `setInterval`
6
+ * scheduler, alarm check, and metrics flush. On each tick it runs the handler and,
7
+ * on success, upserts `job_state.last_run_at` so the *last successful* time
8
+ * survives a restart (the `runOnBoot` + `last_run_at` pair is how a missed daily
9
+ * job catches up: fire on boot, and the handler skips if it already ran recently).
10
+ *
11
+ * Hardening over a bare `setInterval`:
12
+ * - the timer is `unref()`'d, so a poller never keeps the process alive on its own;
13
+ * - ticks are non-reentrant — a slow run can't pile up overlapping fires;
14
+ * - {@link PollingJob.stop} aborts the in-flight run's context signal;
15
+ * - a thrown handler is logged and does NOT advance `last_run_at`.
16
+ *
17
+ * `job_state` must already exist (the app's migration runner builds it).
18
+ */
19
+ export function createPollingJob(opts: PollingJobOpts): PollingJob {
20
+ const logger = opts.logger ?? defaultJobLogger;
21
+ const runOnBoot = opts.runOnBoot !== false;
22
+
23
+ // Preserve any existing `meta` JSON on conflict — only last_run_at moves.
24
+ const upsertState = opts.db.prepare(
25
+ `INSERT INTO job_state (name, last_run_at) VALUES (?, ?)
26
+ ON CONFLICT(name) DO UPDATE SET last_run_at = excluded.last_run_at`,
27
+ );
28
+
29
+ let timer: ReturnType<typeof setInterval> | null = null;
30
+ let controller: AbortController | null = null;
31
+ let inFlight = false;
32
+
33
+ const fire = async (): Promise<void> => {
34
+ if (inFlight || !controller) return;
35
+ inFlight = true;
36
+ // Captured, not re-read: `stop()` nulls `controller` and a later `start()`
37
+ // installs a fresh one, so the identity check below is what tells "still the
38
+ // run I began" from "stopped, and possibly restarted, while I was awaiting".
39
+ const mine = controller;
40
+ const context: JobContext = { db: opts.db, signal: mine.signal, logger };
41
+ try {
42
+ await opts.run(context);
43
+ // 🔴 STOPPED while this run was in flight — do not stamp a completion.
44
+ //
45
+ // Two reasons, and the second is the one that showed up. Semantically, a
46
+ // job whose owner pulled the plug mid-run did not finish on its own terms,
47
+ // so recording `last_run_at` would make the next boot skip work that never
48
+ // happened. Practically, `stop()` is what a host or a test calls just
49
+ // before closing the database — so this statement ran against a closed
50
+ // handle and threw "Database has closed" out of a timer callback, after
51
+ // the test that owned it had ended. That surfaced as an unhandled error
52
+ // reddening cursed-satellites' orch-engine suite roughly one run in three,
53
+ // with a stack pointing here and nothing to do with the test that failed.
54
+ if (controller !== mine) return;
55
+ upsertState.run(opts.name, new Date().toISOString());
56
+ } catch (err) {
57
+ logger.error(`[polling] ${opts.name} failed`, err);
58
+ } finally {
59
+ inFlight = false;
60
+ }
61
+ };
62
+
63
+ return {
64
+ name: opts.name,
65
+ start() {
66
+ if (timer) return;
67
+ controller = new AbortController();
68
+ if (runOnBoot) void fire();
69
+ timer = setInterval(() => void fire(), opts.intervalMs);
70
+ timer.unref?.();
71
+ },
72
+ stop() {
73
+ if (timer) {
74
+ clearInterval(timer);
75
+ timer = null;
76
+ }
77
+ controller?.abort(new Error(`polling job "${opts.name}" stopped`));
78
+ controller = null;
79
+ },
80
+ };
81
+ }