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,284 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import type { Context, Hono } from 'hono';
3
+ import { runMigrations } from '../../migration/runner';
4
+ import { mountPrivateRoutes } from '../../private-media/mountPrivateRoutes';
5
+ import {
6
+ createUnlockService,
7
+ type UnlockPasswordPolicy,
8
+ type UnlockService,
9
+ } from '../../private-media/unlock';
10
+ import { mountMediaRoutes } from '../mountMediaRoutes';
11
+ import { createMediaUrls, type MediaUrls, SourceRegistry } from '../sources';
12
+ import { createStreamTokenService, type StreamTokenService } from '../streamToken';
13
+ import type { MediaCtx, MediaGallerySource, MediaSource, MediaStore, PhotoSource } from '../types';
14
+ import {
15
+ createCatalogueMediaSource,
16
+ createCataloguePhotoSource,
17
+ createCatalogueVideoSource,
18
+ } from './adapter';
19
+ import { createSqliteFileCatalogue, type SqliteFileCatalogueOptions } from './catalogue';
20
+ import {
21
+ type ChunkedUploadService,
22
+ createChunkedUploadService,
23
+ type MintChunkUpload,
24
+ } from './chunkedUpload';
25
+ import { createFileUrls, type FileUrls, mountFileRoutes } from './fileRoutes';
26
+ import { FILE_CATALOGUE_MIGRATIONS } from './migrations';
27
+ import { createStorageOpsLedger } from './storageOps';
28
+ import type { FileCatalogue, PrivateFileCatalogue } from './types';
29
+
30
+ // ── Files stack — the host recipe ───────────────────────────────────────────────
31
+ // One call that assembles the whole files/media server stack a host would otherwise hand-wire:
32
+ // migrations + SQLite catalogue over a MediaStore, the stream-token service, the media/file URL
33
+ // builders, the catalogue-as-video-source registered in a SourceRegistry, the optional private
34
+ // (encrypted) layer, and a `mount()` that wires every byte/upload route onto a Hono app. The auth
35
+ // seams (session → MediaCtx / {userId, userSalt}) stay host-injected — the stack owns no auth.
36
+
37
+ export interface FilesStackOptions {
38
+ /** The catalogue database. The stack runs FILE_CATALOGUE_MIGRATIONS on it itself. */
39
+ db: Database;
40
+ /** Byte backend (disk / binary-server / s3) — the swappable piece. */
41
+ store: MediaStore;
42
+ /** ffmpeg scratch dir for video processing. */
43
+ workDir: string;
44
+ /** Secret for the stream-token service (path-embedded byte-route auth). */
45
+ streamSecret: string;
46
+ /** Owner keyspace segment. Defaults to the userId (files + private are personal) — note the
47
+ * bare primitives default to tenantId; the stack picks the personal axis so every URL/route
48
+ * agrees with the private layer's per-user owner. */
49
+ owner?: (ctx: MediaCtx) => string;
50
+ /** Registered video-source id (also the media-token sourceId). Default 'files'. */
51
+ sourceId?: string;
52
+ /** Video source display title. Default 'Files'. */
53
+ sourceTitle?: string;
54
+ /** Registered photo-source id (the image-filtered view; also its media-token sourceId).
55
+ * Default 'photos'. */
56
+ photoSourceId?: string;
57
+ /** Photo source display title. Default 'Photos'. */
58
+ photoSourceTitle?: string;
59
+ /** Video byte-route mount + URL base (path-only). Default '/_media'. */
60
+ mediaBasePath?: string;
61
+ /** General file byte/upload route mount + URL base (path-only). Default '/_files'. */
62
+ filesBasePath?: string;
63
+ /** PUBLIC base for browser media URLs when it differs from the mount path — a split-origin
64
+ * deploy (SPA on the apex, API on a subdomain) passes the absolute origin+path here
65
+ * ('https://api.example.com/_media') while `mediaBasePath` stays the path-only mount. */
66
+ mediaPublicBase?: string;
67
+ /** PUBLIC base for browser file URLs (split-origin counterpart of `filesBasePath`). */
68
+ filesPublicBase?: string;
69
+ /** Private (encrypted) route mount. Default '/_private'. */
70
+ privateBasePath?: string;
71
+ /** HLS ladder heights for video ingest (createSqliteFileCatalogue passthrough). */
72
+ ladderHeights?: SqliteFileCatalogueOptions['ladderHeights'];
73
+ /** Cap on transcode kicks per file before it's marked failed (createSqliteFileCatalogue
74
+ * passthrough) — the boot-loop guard. Default 3. */
75
+ maxProcessingAttempts?: SqliteFileCatalogueOptions['maxProcessingAttempts'];
76
+ /** Transcode uploaded videos (createSqliteFileCatalogue passthrough). `false` ⇒ videos play
77
+ * raw, no ffmpeg — for memory-constrained hosts. Default true. */
78
+ transcodeVideos?: SqliteFileCatalogueOptions['transcodeVideos'];
79
+ /** Video ingest→derivatives implementation (createSqliteFileCatalogue passthrough) — inject
80
+ * a remote one (binary-server) when the byte store is remote. Default: local ffmpeg. */
81
+ videoProcessor?: SqliteFileCatalogueOptions['videoProcessor'];
82
+ /** Image thumbnail/display derivative implementation (createSqliteFileCatalogue passthrough).
83
+ * Default: local sharp with ffmpeg fallback. */
84
+ imageDerivatives?: SqliteFileCatalogueOptions['imageDerivatives'];
85
+ /** Enables the private (encrypted) stack: unlock service + /_private routes + private uploads.
86
+ * Omit ⇒ `unlock` is null and mount() wires only the public routes. `passwordPolicy` sets what
87
+ * a supplied password MEANS (derive = deniable sets, the default; gate = verify against the
88
+ * app's accounts + recoverable server-key sets — see UnlockPasswordPolicy). */
89
+ privateMedia?: { serverSecret: string; unlockSecret: string; passwordPolicy?: UnlockPasswordPolicy };
90
+ /** Re-kick transcodes a restart left stuck (catalogue.recoverProcessing) at build time.
91
+ * Default true. */
92
+ recoverProcessing?: boolean;
93
+ /** Backfill grid thumbnails at build time for finished PUBLIC rows missing a poster — videos
94
+ * (landed while transcodeVideos was off / failed before the poster step) AND images (e.g. HEIC
95
+ * whose sharp thumbnail failed before the ffmpeg fallback). Default false. */
96
+ backfillPosters?: boolean;
97
+ /** Wire LARGE-file chunked uploads (direct-to-backend). Give the backend-specific per-chunk URL
98
+ * minter (for binary-server: a signed `${base}/upload-chunk/${app}/${key}?token=`) and the stack
99
+ * builds the ChunkedUploadService over its own catalogue + a storage-ops ledger on `db`, then
100
+ * mount() auto-wires the /upload-init, /upload-complete, /upload-abort routes. Omit ⇒ chunked is
101
+ * off and clients fall back to the streamed single PUT (disk-only deploys). */
102
+ chunkedUpload?: {
103
+ mintChunkUpload: MintChunkUpload;
104
+ /** Byte-backend label recorded on each ledger op. Default 'binary'. */
105
+ backend?: string;
106
+ /** Per-chunk size. Default DEFAULT_UPLOAD_CHUNK_SIZE (8 MiB), always < MAX_SINGLE_UPLOAD_BYTES. */
107
+ chunkSize?: number;
108
+ };
109
+ }
110
+
111
+ /** The auth seams `mount()` needs from the host — the only pieces the stack cannot own. */
112
+ export interface FilesStackMountAuth {
113
+ /** Session auth for uploads + listings (same guard mountMediaRoutes/mountFileRoutes take).
114
+ * Return a MediaCtx to allow, null to reject. */
115
+ authorizeUpload: (c: Context) => Promise<MediaCtx | null> | MediaCtx | null;
116
+ /** Session auth for the private layer → {userId, userSalt}. REQUIRED when `privateMedia` is
117
+ * configured (mount() throws otherwise); ignored when it isn't. */
118
+ authorizePrivate?: (c: Context) => Promise<{ userId: string; userSalt: string } | null>;
119
+ /** Wire large-file chunked uploads (mountFileRoutes' `chunked` passthrough). */
120
+ mintChunkUpload?: ChunkedUploadService;
121
+ /** Private-upload password header (default 'x-private-password'). */
122
+ passwordHeader?: string;
123
+ /** Private-upload unlock-token header (default 'x-unlock-token'). */
124
+ tokenHeader?: string;
125
+ }
126
+
127
+ /**
128
+ * The assembled stack. `catalogue` / `fileUrls` / `mediaUrls` / `videoSource` are intentionally
129
+ * named so a cursedshell host can pass the stack straight into `createFilesPlugin(stack)`
130
+ * structurally, and `{ registry }` into `createGalleryPlugin` — the stack does not (and must
131
+ * not, layering) import cursedshell.
132
+ */
133
+ export interface FilesStack {
134
+ catalogue: FileCatalogue & PrivateFileCatalogue;
135
+ token: StreamTokenService;
136
+ mediaUrls: MediaUrls;
137
+ fileUrls: FileUrls;
138
+ videoSource: MediaSource;
139
+ /** The image-filtered sibling view (photo gallery data source). */
140
+ photoSource: PhotoSource;
141
+ /** The MIXED view — photos + videos in one feed (media gallery data source). */
142
+ mediaSource: MediaGallerySource;
143
+ registry: SourceRegistry;
144
+ /** Null unless `privateMedia` was configured. */
145
+ unlock: UnlockService | null;
146
+ /** The large-file chunked-upload service, or null unless `chunkedUpload` was configured. mount()
147
+ * auto-wires it into the file routes; exposed for observability (pending()) + tests. */
148
+ chunked: ChunkedUploadService | null;
149
+ /**
150
+ * Mount every byte/upload route the stack owns onto a Hono app: mountMediaRoutes
151
+ * (mediaBasePath) + mountFileRoutes (filesBasePath, incl. privateUpload when unlock exists) +
152
+ * mountPrivateRoutes (privateBasePath, when unlock exists). Auth stays host-injected.
153
+ */
154
+ mount(app: Hono, auth: FilesStackMountAuth): void;
155
+ }
156
+
157
+ /** Assemble the whole files/media server stack from one options bag. See {@link FilesStack}. */
158
+ export function createFilesStack(opts: FilesStackOptions): FilesStack {
159
+ const owner = opts.owner ?? ((c: MediaCtx) => c.userId);
160
+ const sourceId = opts.sourceId ?? 'files';
161
+ const mediaBasePath = opts.mediaBasePath ?? '/_media';
162
+ const filesBasePath = opts.filesBasePath ?? '/_files';
163
+ const { db, store, workDir } = opts;
164
+
165
+ const token = createStreamTokenService({ secret: opts.streamSecret });
166
+ const mediaUrls = createMediaUrls({
167
+ baseUrl: opts.mediaPublicBase ?? mediaBasePath,
168
+ token,
169
+ owner,
170
+ });
171
+ const fileUrls = createFileUrls({ baseUrl: opts.filesPublicBase ?? filesBasePath, token, owner });
172
+
173
+ runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
174
+ const catalogue = createSqliteFileCatalogue({
175
+ db,
176
+ store,
177
+ workDir,
178
+ owner,
179
+ ladderHeights: opts.ladderHeights,
180
+ maxProcessingAttempts: opts.maxProcessingAttempts,
181
+ transcodeVideos: opts.transcodeVideos,
182
+ videoProcessor: opts.videoProcessor,
183
+ imageDerivatives: opts.imageDerivatives,
184
+ });
185
+ if (opts.recoverProcessing !== false) catalogue.recoverProcessing();
186
+ if (opts.backfillPosters) {
187
+ catalogue.backfillVideoPosters();
188
+ catalogue.backfillImageThumbnails();
189
+ }
190
+
191
+ const videoSource = createCatalogueVideoSource({
192
+ id: sourceId,
193
+ title: opts.sourceTitle ?? 'Files',
194
+ catalogue,
195
+ urls: mediaUrls,
196
+ });
197
+ const photoSource = createCataloguePhotoSource({
198
+ id: opts.photoSourceId ?? 'photos',
199
+ title: opts.photoSourceTitle ?? 'Photos',
200
+ catalogue,
201
+ fileUrls,
202
+ mediaUrls,
203
+ });
204
+ const mediaSource = createCatalogueMediaSource({
205
+ id: opts.photoSourceId ?? 'photos',
206
+ title: 'Media',
207
+ catalogue,
208
+ urls: mediaUrls,
209
+ fileUrls,
210
+ // Videos in the mixed feed stream through the registered video source's routes.
211
+ videoSourceId: sourceId,
212
+ });
213
+ const registry = new SourceRegistry().register(videoSource);
214
+
215
+ const unlock = opts.privateMedia ? createUnlockService(opts.privateMedia) : null;
216
+
217
+ const chunked = opts.chunkedUpload
218
+ ? createChunkedUploadService({
219
+ catalogue,
220
+ ledger: createStorageOpsLedger(db),
221
+ backend: opts.chunkedUpload.backend ?? 'binary',
222
+ mintChunkUpload: opts.chunkedUpload.mintChunkUpload,
223
+ chunkSize: opts.chunkedUpload.chunkSize,
224
+ owner,
225
+ })
226
+ : null;
227
+
228
+ return {
229
+ catalogue,
230
+ token,
231
+ mediaUrls,
232
+ fileUrls,
233
+ videoSource,
234
+ photoSource,
235
+ mediaSource,
236
+ registry,
237
+ unlock,
238
+ chunked,
239
+ mount(app, auth) {
240
+ if (unlock && !auth.authorizePrivate) {
241
+ throw new Error(
242
+ 'createFilesStack: privateMedia is configured but mount() got no authorizePrivate — ' +
243
+ 'the private routes need a session→{userId, userSalt} resolver.',
244
+ );
245
+ }
246
+ mountMediaRoutes(app, {
247
+ store,
248
+ token,
249
+ basePath: mediaBasePath,
250
+ registry,
251
+ authorizeUpload: auth.authorizeUpload,
252
+ });
253
+ mountFileRoutes(app, {
254
+ store,
255
+ token,
256
+ basePath: filesBasePath,
257
+ catalogue,
258
+ authorizeUpload: auth.authorizeUpload,
259
+ // Prefer a mount-time override, else the service the stack built from `chunkedUpload` opts.
260
+ chunked: auth.mintChunkUpload ?? chunked ?? undefined,
261
+ privateUpload:
262
+ unlock && auth.authorizePrivate
263
+ ? {
264
+ unlock,
265
+ authorize: auth.authorizePrivate,
266
+ passwordHeader: auth.passwordHeader,
267
+ tokenHeader: auth.tokenHeader,
268
+ passwordPolicy: opts.privateMedia?.passwordPolicy,
269
+ }
270
+ : undefined,
271
+ });
272
+ if (unlock && auth.authorizePrivate) {
273
+ mountPrivateRoutes(app, {
274
+ store,
275
+ index: catalogue.asPrivateIndex(),
276
+ unlock,
277
+ basePath: opts.privateBasePath,
278
+ authorize: auth.authorizePrivate,
279
+ passwordPolicy: opts.privateMedia?.passwordPolicy,
280
+ });
281
+ }
282
+ },
283
+ };
284
+ }
@@ -0,0 +1,126 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { beforeEach, describe, expect, test } from 'bun:test';
3
+ import { runMigrations } from '../../migration/runner';
4
+ import type { MediaStore } from '../types';
5
+ import { FILE_CATALOGUE_MIGRATIONS } from './migrations';
6
+ import {
7
+ createLedgeredDeletes,
8
+ createStorageOpsLedger,
9
+ createStorageReconciler,
10
+ } from './storageOps';
11
+
12
+ // A fake store whose remove() fails the first `failTimes` calls per key, then succeeds — models a
13
+ // flaky remote backend (binary-server) so the reconciler's heal path is exercised deterministically.
14
+ function flakyStore(failTimes: number): MediaStore & { removed: Set<string> } {
15
+ const removed = new Set<string>();
16
+ const attempts = new Map<string, number>();
17
+ const fail = (key: string) => {
18
+ const n = (attempts.get(key) ?? 0) + 1;
19
+ attempts.set(key, n);
20
+ if (n <= failTimes) throw new Error(`transient failure ${n} for ${key}`);
21
+ };
22
+ return {
23
+ removed,
24
+ async put() {},
25
+ async putStream() {},
26
+ async get() {
27
+ return null;
28
+ },
29
+ async remove(key) {
30
+ fail(key);
31
+ removed.add(key);
32
+ },
33
+ async removePrefix(prefix) {
34
+ fail(prefix);
35
+ removed.add(prefix);
36
+ },
37
+ async stat() {
38
+ return null;
39
+ },
40
+ async getRange() {
41
+ return null;
42
+ },
43
+ };
44
+ }
45
+
46
+ describe('storage-ops ledger', () => {
47
+ let db: Database;
48
+ let clock = 0;
49
+ beforeEach(() => {
50
+ db = new Database(':memory:');
51
+ runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
52
+ clock = 0;
53
+ });
54
+
55
+ test('records pending, marks done/failed, and reports outstanding correctly', () => {
56
+ const ledger = createStorageOpsLedger(db, () => ++clock);
57
+ const a = ledger.record({ owner: 'u1', action: 'delete', key: 'fs/u1/a', backend: 'disk' });
58
+ const b = ledger.record({ owner: 'u1', action: 'delete', key: 'fs/u1/b', backend: 'disk' });
59
+
60
+ expect(ledger.get(a)?.status).toBe('pending');
61
+ ledger.markDone(a);
62
+ expect(ledger.get(a)?.status).toBe('done');
63
+ ledger.markFailed(b, 'boom');
64
+ expect(ledger.get(b)?.status).toBe('failed');
65
+ expect(ledger.get(b)?.attempts).toBe(1);
66
+
67
+ // `done` excluded; `failed` under the cap still outstanding.
68
+ expect(ledger.outstanding().map((o) => o.opId)).toEqual([b]);
69
+ // Over the attempt cap → dropped from outstanding (a poison op won't spin forever).
70
+ expect(ledger.outstanding({ maxAttempts: 1 })).toEqual([]);
71
+ });
72
+
73
+ test('`done` is a monotonic terminal state — a late failure can never flip it back', () => {
74
+ const ledger = createStorageOpsLedger(db, () => ++clock);
75
+ const op = ledger.record({ owner: 'u1', action: 'upload', key: 'fs/u1/x', backend: 'binary' });
76
+ ledger.markDone(op);
77
+ // A duplicate completion webhook racing the reconciler (or a stale failure) must NOT corrupt it.
78
+ ledger.markFailed(op, 'late webhook says failed');
79
+ expect(ledger.get(op)?.status).toBe('done');
80
+ expect(ledger.get(op)?.attempts).toBe(0);
81
+ ledger.markDone(op); // idempotent
82
+ expect(ledger.get(op)?.status).toBe('done');
83
+ });
84
+
85
+ test('ledgered deletes mark done on success and keep a failed row outstanding', async () => {
86
+ const ledger = createStorageOpsLedger(db, () => ++clock);
87
+ const store = flakyStore(0); // never fails
88
+ const del = createLedgeredDeletes(store, ledger, 'binary');
89
+ await del.removeFor('u1', 'f1', 'fs/u1/f1');
90
+ expect(store.removed.has('fs/u1/f1')).toBe(true);
91
+ expect(ledger.outstanding()).toEqual([]); // marked done
92
+
93
+ const flaky = createLedgeredDeletes(flakyStore(99), ledger, 'binary');
94
+ await expect(flaky.removeFor('u1', 'f2', 'fs/u1/f2')).rejects.toThrow();
95
+ // The failed delete survives for the reconciler.
96
+ expect(ledger.outstanding().map((o) => o.key)).toEqual(['fs/u1/f2']);
97
+ });
98
+
99
+ test('reconciler heals a transiently-failed delete on a later pass', async () => {
100
+ const ledger = createStorageOpsLedger(db, () => ++clock);
101
+ const store = flakyStore(1); // fails once per key, then succeeds
102
+ const del = createLedgeredDeletes(store, ledger, 'binary');
103
+
104
+ await expect(del.removeFor('u1', 'f3', 'fs/u1/f3')).rejects.toThrow(); // first attempt fails
105
+ expect(ledger.get(ledger.outstanding()[0].opId)?.status).toBe('failed');
106
+
107
+ // Reconcile → the retry succeeds (flaky store now lets it through) → healed, nothing left.
108
+ const reconciler = createStorageReconciler(store, ledger);
109
+ expect(await reconciler.reconcile()).toEqual({ healed: 1, failed: 0 });
110
+ expect(ledger.outstanding()).toEqual([]);
111
+ expect(store.removed.has('fs/u1/f3')).toBe(true);
112
+ });
113
+
114
+ test('reconciler bumps attempts and gives up past the cap (no infinite retry)', async () => {
115
+ const ledger = createStorageOpsLedger(db, () => ++clock);
116
+ const store = flakyStore(99); // always fails
117
+ ledger.record({ owner: 'u1', action: 'delete', key: 'fs/u1/dead', backend: 'binary' });
118
+
119
+ const reconciler = createStorageReconciler(store, ledger);
120
+ // Each pass bumps attempts; after 3 passes attempts reaches the cap and it drops out (no
121
+ // infinite retry of a poison op).
122
+ for (let i = 0; i < 3; i++) await reconciler.reconcile({ maxAttempts: 3 });
123
+ expect(ledger.outstanding({ maxAttempts: 3 })).toEqual([]);
124
+ expect(store.removed.has('fs/u1/dead')).toBe(false); // never actually removed
125
+ });
126
+ });
@@ -0,0 +1,213 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import type { MediaStore } from '../types';
3
+
4
+ // ── Storage-operations ledger + reconciler ───────────────────────────────────────
5
+ // A durable write-ahead log for CROSS-BOUNDARY byte operations (delete / delete-prefix / an
6
+ // assembled chunked upload) against a remote backend like binary-server, where the call can
7
+ // succeed on our side but the ack never arrives — or fail transiently — leaving bytes orphaned
8
+ // or a delete un-applied. Before each such op we record a `pending` row; on success we mark it
9
+ // `done`; a crash / failed call leaves it `pending`, and the reconciler re-drives it to
10
+ // completion on the next boot (idempotent — remove/removePrefix must not throw on a missing key).
11
+ //
12
+ // Uploads that fail simply error the request (no ledger needed — nothing to heal). The ledger
13
+ // targets the ops whose FAILURE silently leaks: a delete binary-server never applied, or an
14
+ // assemble webhook we recorded but must confirm. The `storage_operations` table is created by the
15
+ // FileCatalogue migration; binary-server echoes our `op_id` in its own event ledger so the two
16
+ // can be reconciled against each other (see binary-server db.ts).
17
+
18
+ export type StorageAction = 'upload' | 'delete' | 'delete_prefix';
19
+ export type StorageOpStatus = 'pending' | 'done' | 'failed';
20
+
21
+ export type StorageOpRow = {
22
+ opId: string;
23
+ owner: string;
24
+ fileId: string | null;
25
+ action: StorageAction;
26
+ key: string;
27
+ backend: string;
28
+ status: StorageOpStatus;
29
+ attempts: number;
30
+ error: string | null;
31
+ createdAt: number;
32
+ updatedAt: number;
33
+ };
34
+
35
+ type DbRow = {
36
+ op_id: string;
37
+ owner: string;
38
+ file_id: string | null;
39
+ action: string;
40
+ key: string;
41
+ backend: string;
42
+ status: string;
43
+ attempts: number;
44
+ error: string | null;
45
+ created_at: number;
46
+ updated_at: number;
47
+ };
48
+
49
+ const toRow = (r: DbRow): StorageOpRow => ({
50
+ opId: r.op_id,
51
+ owner: r.owner,
52
+ fileId: r.file_id,
53
+ action: r.action as StorageAction,
54
+ key: r.key,
55
+ backend: r.backend,
56
+ status: r.status as StorageOpStatus,
57
+ attempts: r.attempts,
58
+ error: r.error,
59
+ createdAt: r.created_at,
60
+ updatedAt: r.updated_at,
61
+ });
62
+
63
+ export interface StorageOpsLedger {
64
+ /** Record a pending op and return its id (caller may pass one, e.g. to share with binary-server
65
+ * as `x-op-id`; otherwise a uuid is minted). */
66
+ record(op: {
67
+ owner: string;
68
+ fileId?: string | null;
69
+ action: StorageAction;
70
+ key: string;
71
+ backend: string;
72
+ opId?: string;
73
+ }): string;
74
+ markDone(opId: string): void;
75
+ markFailed(opId: string, error: string): void;
76
+ get(opId: string): StorageOpRow | null;
77
+ /** Ops still needing work: `pending`, or `failed` under the attempt cap, created before `before`
78
+ * (default: everything). Oldest first. */
79
+ outstanding(opts?: { maxAttempts?: number; before?: number }): StorageOpRow[];
80
+ }
81
+
82
+ export function createStorageOpsLedger(
83
+ db: Database,
84
+ now: () => number = () => Date.now(),
85
+ ): StorageOpsLedger {
86
+ const insert = db.query(
87
+ `INSERT INTO storage_operations (op_id, owner, file_id, action, key, backend, status, attempts, created_at, updated_at)
88
+ VALUES ($op, $owner, $file, $action, $key, $backend, 'pending', 0, $now, $now)`,
89
+ );
90
+ // MONOTONIC terminal state: once an op is `done` it can never flip back (a duplicate completion
91
+ // webhook racing the reconciler, or a late failure, must not corrupt a settled success). `failed`
92
+ // is NOT terminal — the reconciler retries a failed delete and can heal it to `done`, or bump its
93
+ // attempts — so both writes guard only against overwriting `done`. (Ported from ca's resolveOp
94
+ // `CASE WHEN status='pending'` invariant, adapted to our two-writer model.)
95
+ const setDone = db.query(
96
+ `UPDATE storage_operations SET status = 'done', updated_at = $now WHERE op_id = $op AND status != 'done'`,
97
+ );
98
+ const setFailed = db.query(
99
+ `UPDATE storage_operations SET status = 'failed', attempts = attempts + 1, error = $error, updated_at = $now WHERE op_id = $op AND status != 'done'`,
100
+ );
101
+ const getOne = db.query<DbRow, { $op: string }>(
102
+ `SELECT * FROM storage_operations WHERE op_id = $op`,
103
+ );
104
+
105
+ return {
106
+ record(op) {
107
+ const opId = op.opId ?? crypto.randomUUID();
108
+ insert.run({
109
+ $op: opId,
110
+ $owner: op.owner,
111
+ $file: op.fileId ?? null,
112
+ $action: op.action,
113
+ $key: op.key,
114
+ $backend: op.backend,
115
+ $now: now(),
116
+ });
117
+ return opId;
118
+ },
119
+ markDone(opId) {
120
+ setDone.run({ $op: opId, $now: now() });
121
+ },
122
+ markFailed(opId, error) {
123
+ setFailed.run({ $op: opId, $error: error.slice(0, 500), $now: now() });
124
+ },
125
+ get(opId) {
126
+ const r = getOne.get({ $op: opId });
127
+ return r ? toRow(r) : null;
128
+ },
129
+ outstanding(opts = {}) {
130
+ const maxAttempts = opts.maxAttempts ?? 5;
131
+ const before = opts.before ?? now() + 1;
132
+ return db
133
+ .query<DbRow, { $max: number; $before: number }>(
134
+ `SELECT * FROM storage_operations
135
+ WHERE status != 'done' AND attempts < $max AND created_at < $before
136
+ ORDER BY created_at ASC`,
137
+ )
138
+ .all({ $max: maxAttempts, $before: before })
139
+ .map(toRow);
140
+ },
141
+ };
142
+ }
143
+
144
+ // Wrap a MediaStore so DELETE / DELETE-PREFIX are ledgered: a `pending` row is written first, the
145
+ // op attempted, then marked `done`; a failure marks it `failed` and rethrows (the row survives for
146
+ // the reconciler). Reads/writes pass straight through — only the leak-prone deletes are logged.
147
+ export function createLedgeredDeletes(
148
+ store: MediaStore,
149
+ ledger: StorageOpsLedger,
150
+ backend: string,
151
+ ): Pick<MediaStore, 'remove' | 'removePrefix'> & {
152
+ removeFor(owner: string, fileId: string | null, key: string): Promise<void>;
153
+ removePrefixFor(owner: string, fileId: string | null, prefix: string): Promise<void>;
154
+ } {
155
+ const run = async (
156
+ action: StorageAction,
157
+ owner: string,
158
+ fileId: string | null,
159
+ key: string,
160
+ exec: () => Promise<void>,
161
+ ) => {
162
+ const opId = ledger.record({ owner, fileId, action, key, backend });
163
+ try {
164
+ await exec();
165
+ ledger.markDone(opId);
166
+ } catch (e) {
167
+ ledger.markFailed(opId, e instanceof Error ? e.message : String(e));
168
+ throw e;
169
+ }
170
+ };
171
+ return {
172
+ remove: (key) => run('delete', '', null, key, () => store.remove(key)),
173
+ removePrefix: (prefix) =>
174
+ run('delete_prefix', '', null, prefix, () => store.removePrefix(prefix)),
175
+ removeFor: (owner, fileId, key) => run('delete', owner, fileId, key, () => store.remove(key)),
176
+ removePrefixFor: (owner, fileId, prefix) =>
177
+ run('delete_prefix', owner, fileId, prefix, () => store.removePrefix(prefix)),
178
+ };
179
+ }
180
+
181
+ export interface StorageReconciler {
182
+ /** Re-drive every outstanding op to completion (idempotent). Returns how many healed vs. still
183
+ * failing. Safe to call on boot and on an interval. */
184
+ reconcile(opts?: { maxAttempts?: number }): Promise<{ healed: number; failed: number }>;
185
+ }
186
+
187
+ export function createStorageReconciler(
188
+ store: MediaStore,
189
+ ledger: StorageOpsLedger,
190
+ ): StorageReconciler {
191
+ return {
192
+ async reconcile(opts = {}) {
193
+ let healed = 0;
194
+ let failed = 0;
195
+ for (const op of ledger.outstanding({ maxAttempts: opts.maxAttempts })) {
196
+ // Only DELETES are re-driven by re-issuing the store call (idempotent). An 'upload' op is
197
+ // healed by its completion webhook (or an app-level check against binary-server's own event
198
+ // ledger) — re-issuing store.remove here would wrongly delete the just-uploaded bytes.
199
+ if (op.action === 'upload') continue;
200
+ try {
201
+ if (op.action === 'delete') await store.remove(op.key);
202
+ else await store.removePrefix(op.key);
203
+ ledger.markDone(op.opId);
204
+ healed++;
205
+ } catch (e) {
206
+ ledger.markFailed(op.opId, e instanceof Error ? e.message : String(e));
207
+ failed++;
208
+ }
209
+ }
210
+ return { healed, failed };
211
+ },
212
+ };
213
+ }