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,56 @@
1
+ import type { MediaCtx } from '../types';
2
+ import type { StorageOpsLedger } from './storageOps';
3
+ import type { FileCatalogue, FileRecord } from './types';
4
+ export type ChunkedInit = {
5
+ fileId: string;
6
+ /** Chunk session id — also the ledger op id; echoed to the backend as sid so its assemble event
7
+ * matches our storage_operations row. */
8
+ sid: string;
9
+ /** The storage key the assembled bytes land at (fsSourceKey for video, else fileBinaryKey). */
10
+ key: string;
11
+ chunkSize: number;
12
+ chunkCount: number;
13
+ /** One direct-to-backend PUT URL per chunk, in index order. */
14
+ chunkUploadUrls: string[];
15
+ };
16
+ export interface ChunkedUploadService {
17
+ /** Begin a chunked upload: reserve an id, record a pending `upload` op, and mint the per-chunk
18
+ * direct-to-backend PUT URLs. */
19
+ init(ctx: MediaCtx, req: {
20
+ name: string;
21
+ mime: string;
22
+ size: number;
23
+ tags?: string[];
24
+ }): Promise<ChunkedInit>;
25
+ /** Finish it: mark the op done and register the catalogue row over the assembled bytes. Returns
26
+ * the new record, or null if the session is unknown (already completed / server restarted). */
27
+ complete(sid: string, confirmed?: {
28
+ size?: number;
29
+ }): Promise<FileRecord | null>;
30
+ /** Give up on a session (client cancelled / a chunk failed) — mark the ledger op failed. */
31
+ abort(sid: string): void;
32
+ /** Open session count (observability / tests). */
33
+ pending(): number;
34
+ }
35
+ /**
36
+ * Mint the direct-to-backend PUT URL for one chunk. The app supplies this so the module stays
37
+ * backend-agnostic. For binary-server: `${base}/upload-chunk/${app}/${key}?token=` with a token
38
+ * carrying { k, uid, sid, ci, tc }.
39
+ */
40
+ export type MintChunkUpload = (args: {
41
+ key: string;
42
+ sid: string;
43
+ ci: number;
44
+ tc: number;
45
+ owner: string;
46
+ }) => Promise<string>;
47
+ export declare function createChunkedUploadService(cfg: {
48
+ catalogue: FileCatalogue;
49
+ ledger: StorageOpsLedger;
50
+ /** Byte backend label recorded on the ledger op (e.g. 'binary'). */
51
+ backend: string;
52
+ mintChunkUpload: MintChunkUpload;
53
+ chunkSize?: number;
54
+ owner?: (ctx: MediaCtx) => string;
55
+ now?: () => number;
56
+ }): ChunkedUploadService;
@@ -0,0 +1,70 @@
1
+ import { DEFAULT_UPLOAD_CHUNK_SIZE } from 'cursedbelt-core/media/upload-limits';
2
+ import { catalogueUploadKey } from './catalogue';
3
+ // ── Chunked upload (large files, direct-to-backend) ──────────────────────────────
4
+ // For multi-GB uploads we don't proxy bytes through the app origin. Instead: the client asks the
5
+ // app to START an upload (init) → the app mints per-chunk PUT URLs that go DIRECT to the byte
6
+ // backend (binary-server), records a durable `upload` ledger op, and hands the URLs back. The
7
+ // client PUTs each chunk to the backend, which assembles them at the target key once the last
8
+ // arrives. Then the client (or a backend webhook) calls COMPLETE, and the app registers the
9
+ // catalogue row over the already-stored bytes (no re-upload) + kicks processing.
10
+ //
11
+ // This module is backend-AGNOSTIC: the app injects `mintChunkUpload` (the binary-server chunk URL
12
+ // + signed sid/ci/tc token). On disk-only deploys, an app simply doesn't wire chunked uploads and
13
+ // keeps the streamed `PUT /upload`. Sessions are in-memory (the transient name/mime/size); the
14
+ // ledger row is the DURABLE record of intent, so an abandoned upload is observable/reconcilable.
15
+ const DEFAULT_CHUNK_SIZE = DEFAULT_UPLOAD_CHUNK_SIZE;
16
+ export function createChunkedUploadService(cfg) {
17
+ const ownerOf = cfg.owner ?? ((c) => c.tenantId);
18
+ const now = cfg.now ?? (() => Date.now());
19
+ const chunkSize = cfg.chunkSize ?? DEFAULT_CHUNK_SIZE;
20
+ const sessions = new Map();
21
+ return {
22
+ async init(ctx, req) {
23
+ const owner = ownerOf(ctx);
24
+ const fileId = crypto.randomUUID();
25
+ const sid = crypto.randomUUID();
26
+ const key = catalogueUploadKey(owner, fileId, req.mime);
27
+ const chunkCount = Math.max(1, Math.ceil((req.size || 1) / chunkSize));
28
+ // Durable record of intent — its op_id (sid) is what the backend echoes on assemble.
29
+ cfg.ledger.record({ owner, fileId, action: 'upload', key, backend: cfg.backend, opId: sid });
30
+ sessions.set(sid, {
31
+ fileId,
32
+ owner,
33
+ ctx,
34
+ name: req.name,
35
+ mime: req.mime,
36
+ size: req.size,
37
+ key,
38
+ chunkCount,
39
+ tags: req.tags,
40
+ createdAt: now(),
41
+ });
42
+ const chunkUploadUrls = await Promise.all(Array.from({ length: chunkCount }, (_, ci) => cfg.mintChunkUpload({ key, sid, ci, tc: chunkCount, owner })));
43
+ return { fileId, sid, key, chunkSize, chunkCount, chunkUploadUrls };
44
+ },
45
+ async complete(sid, confirmed) {
46
+ const s = sessions.get(sid);
47
+ if (!s)
48
+ return null;
49
+ cfg.ledger.markDone(sid);
50
+ const rec = await cfg.catalogue.registerUploaded(s.ctx, {
51
+ id: s.fileId,
52
+ name: s.name,
53
+ mime: s.mime,
54
+ size: confirmed?.size ?? s.size,
55
+ tags: s.tags,
56
+ });
57
+ sessions.delete(sid);
58
+ return rec;
59
+ },
60
+ abort(sid) {
61
+ if (!sessions.has(sid))
62
+ return;
63
+ cfg.ledger.markFailed(sid, 'aborted');
64
+ sessions.delete(sid);
65
+ },
66
+ pending() {
67
+ return sessions.size;
68
+ },
69
+ };
70
+ }
@@ -0,0 +1,51 @@
1
+ import type { Context, Hono } from 'hono';
2
+ import type { UnlockPasswordPolicy, UnlockService } from '../../private-media/unlock';
3
+ import type { StreamTokenService } from '../streamToken';
4
+ import type { MediaCtx, MediaStore } from '../types';
5
+ import type { ChunkedUploadService } from './chunkedUpload';
6
+ import { type FileCatalogue, type PrivateFileCatalogue } from './types';
7
+ /** The 413 message a single-PUT over the cap returns — the client treats it as a hard error. */
8
+ export declare const SINGLE_UPLOAD_TOO_LARGE = "file too large for a single request \u2014 use chunked upload";
9
+ export interface FileUrls {
10
+ /** A GET URL for a file's primary binary, carrying its mime as `?ct=`. */
11
+ build(ctx: MediaCtx, fileId: string, mime: string): Promise<{
12
+ url: string;
13
+ }>;
14
+ }
15
+ export declare function createFileUrls(opts: {
16
+ /** Path-only ('/_files') same-origin, or an absolute origin+path for split-origin. */
17
+ baseUrl: string;
18
+ token: StreamTokenService;
19
+ owner?: (ctx: MediaCtx) => string;
20
+ }): FileUrls;
21
+ export type MountFileRoutesOptions = {
22
+ store: MediaStore;
23
+ token: StreamTokenService;
24
+ /** Path mount (default '/_files'). */
25
+ basePath?: string;
26
+ /** Session auth for the upload PUTs (a fetch CAN send the Bearer). Return a ctx or null. */
27
+ authorizeUpload?: (c: Context) => Promise<MediaCtx | null> | MediaCtx | null;
28
+ /** The catalogue uploads land in. Required for the upload routes to be mounted. */
29
+ catalogue?: FileCatalogue & PrivateFileCatalogue;
30
+ /** Wire private uploads: derive a per-user set key from the request (password header ⇒ deniable,
31
+ * absent ⇒ standard server key). Omit ⇒ the private-upload route is disabled. */
32
+ privateUpload?: {
33
+ unlock: UnlockService;
34
+ authorize: (c: Context) => Promise<{
35
+ userId: string;
36
+ userSalt: string;
37
+ } | null>;
38
+ /** Header carrying the plaintext password for a deniable set (default 'x-private-password'). */
39
+ passwordHeader?: string;
40
+ /** Header carrying an UNLOCK TOKEN (an already-unlocked session) — takes precedence over the
41
+ * password header; setKey + keyMode come from the verified grant (default 'x-unlock-token'). */
42
+ tokenHeader?: string;
43
+ /** What a supplied password means (default derive — deniable set). `gate` verifies it
44
+ * against the app's accounts and wraps under the recoverable server key instead. */
45
+ passwordPolicy?: UnlockPasswordPolicy;
46
+ };
47
+ /** Wire large-file chunked uploads (direct-to-backend). Omit ⇒ init/complete routes are absent
48
+ * and clients use the streamed `PUT /upload`. Uses the same `authorizeUpload` guard. */
49
+ chunked?: ChunkedUploadService;
50
+ };
51
+ export declare function mountFileRoutes(app: Hono, opts: MountFileRoutesOptions): void;
@@ -0,0 +1,405 @@
1
+ import { MAX_SINGLE_UPLOAD_BYTES } from 'cursedbelt-core/media/upload-limits';
2
+ import { serveRange } from '../streamRange';
3
+ import { fileBinaryKey } from './catalogue';
4
+ import { FILE_SORTS, } from './types';
5
+ // ── File binary + upload routes ─────────────────────────────────────────────────
6
+ // The FileCatalogue's byte edge for GENERAL files (images/docs/audio/any) — the piece
7
+ // mountMediaRoutes (video-only: mp4/hls/poster/sprite) doesn't cover. A file's primary binary
8
+ // lives at fs/<owner>/<id> and is served here, token-authorized like the media routes (an <img>
9
+ // can't send a Bearer). Uploads DO carry the app Bearer, so the PUT routes are session-guarded.
10
+ //
11
+ // `sourceId` for a file token is the constant 'files'. The served Content-Type is carried as a
12
+ // signed-URL query (`?ct=`) — the catalogue knows each file's mime when it builds the URL, so the
13
+ // route needn't re-derive it. It only ever serves the token owner's own bytes.
14
+ const FILE_SOURCE_ID = 'files';
15
+ // Guard the echoed Content-Type: a plain type/subtype, no header injection.
16
+ const SAFE_MIME = /^[\w.+-]+\/[\w.+-]+$/;
17
+ /** The 413 message a single-PUT over the cap returns — the client treats it as a hard error. */
18
+ export const SINGLE_UPLOAD_TOO_LARGE = 'file too large for a single request — use chunked upload';
19
+ /** Thrown into the body stream when it exceeds the single-request cap mid-stream (no Content-Length
20
+ * to fail fast on). Handlers catch it and turn it into a clean 413 instead of a 500. */
21
+ class UploadTooLargeError extends Error {
22
+ constructor() {
23
+ super(SINGLE_UPLOAD_TOO_LARGE);
24
+ this.name = 'UploadTooLargeError';
25
+ }
26
+ }
27
+ const isUploadTooLarge = (e) => e instanceof UploadTooLargeError || (e instanceof Error && e.message === SINGLE_UPLOAD_TOO_LARGE);
28
+ /**
29
+ * Enforce the single-request upload cap (MAX_SINGLE_UPLOAD_BYTES) on a streamed PUT body. The law:
30
+ * no single HTTP request may carry more than the cap — larger files MUST chunk. Fails FAST on the
31
+ * Content-Length header (browsers set it for a File/Blob body), and — for length-less chunked
32
+ * transfer — wraps the stream to abort once the accumulated bytes pass the cap (the wrapping error
33
+ * surfaces from `catalogue.ingest`, which the handlers turn into a 413). Returns either the body to
34
+ * hand to the catalogue, or a 413 Response to return immediately.
35
+ */
36
+ function guardSingleUpload(c, body) {
37
+ const declared = Number(c.req.header('content-length'));
38
+ if (Number.isFinite(declared) && declared > MAX_SINGLE_UPLOAD_BYTES) {
39
+ return c.json({ error: SINGLE_UPLOAD_TOO_LARGE }, 413);
40
+ }
41
+ let seen = 0;
42
+ return body.pipeThrough(new TransformStream({
43
+ transform(chunk, ctrl) {
44
+ seen += chunk.byteLength;
45
+ if (seen > MAX_SINGLE_UPLOAD_BYTES) {
46
+ ctrl.error(new UploadTooLargeError());
47
+ return;
48
+ }
49
+ ctrl.enqueue(chunk);
50
+ },
51
+ }));
52
+ }
53
+ export function createFileUrls(opts) {
54
+ const ownerOf = opts.owner ?? ((c) => c.tenantId);
55
+ const base = opts.baseUrl.replace(/\/$/, '');
56
+ return {
57
+ async build(ctx, fileId, mime) {
58
+ const token = await opts.token.sign({
59
+ owner: ownerOf(ctx),
60
+ sourceId: FILE_SOURCE_ID,
61
+ videoId: fileId,
62
+ });
63
+ return { url: `${base}/s/${token}/${fileId}?ct=${encodeURIComponent(mime)}` };
64
+ },
65
+ };
66
+ }
67
+ export function mountFileRoutes(app, opts) {
68
+ const base = opts.basePath ?? '/_files';
69
+ const { store, token, catalogue } = opts;
70
+ // GET a file's primary binary (Range-capable). Prefers a backend redirect (binary-server edge).
71
+ app.get(`${base}/s/:token/:id`, async (c) => {
72
+ const grant = await token.verify(c.req.param('token'));
73
+ if (!grant || grant.videoId !== c.req.param('id'))
74
+ return c.body(null, 401);
75
+ const ct = c.req.query('ct');
76
+ const mime = ct && SAFE_MIME.test(ct) ? ct : 'application/octet-stream';
77
+ const key = fileBinaryKey(grant.owner, grant.videoId);
78
+ if (store.redirectUrl) {
79
+ const url = await store.redirectUrl(key, { mime });
80
+ if (url)
81
+ return c.redirect(url, 303);
82
+ }
83
+ return serveRange(store, key, mime, c.req.header('range'), { contentDisposition: 'inline' });
84
+ });
85
+ // PUT a regular (public) upload → catalogue.ingest (any file type). Streamed, not buffered.
86
+ if (opts.authorizeUpload && catalogue) {
87
+ app.put(`${base}/upload`, async (c) => {
88
+ const ctx = await opts.authorizeUpload?.(c);
89
+ if (!ctx)
90
+ return c.json({ error: 'unauthorized' }, 401);
91
+ const raw = c.req.raw.body;
92
+ if (!raw)
93
+ return c.json({ error: 'empty body' }, 400);
94
+ const body = guardSingleUpload(c, raw);
95
+ if (body instanceof Response)
96
+ return body;
97
+ const tags = c.req.header('x-tags');
98
+ try {
99
+ const file = await catalogue.ingest(ctx, {
100
+ name: c.req.header('x-filename') ?? 'upload',
101
+ mime: c.req.header('content-type') ?? 'application/octet-stream',
102
+ body,
103
+ tags: tags
104
+ ? tags
105
+ .split(',')
106
+ .map((t) => t.trim())
107
+ .filter(Boolean)
108
+ : undefined,
109
+ });
110
+ return c.json({ file });
111
+ }
112
+ catch (e) {
113
+ if (isUploadTooLarge(e))
114
+ return c.json({ error: SINGLE_UPLOAD_TOO_LARGE }, 413);
115
+ throw e;
116
+ }
117
+ });
118
+ // PUT new bytes OVER an existing file (the editor's "overwrite original"). Same id — tags,
119
+ // collections, favorites and view history survive; derivatives wipe + re-derive. Streamed.
120
+ app.put(`${base}/replace/:id`, async (c) => {
121
+ const ctx = await opts.authorizeUpload?.(c);
122
+ if (!ctx)
123
+ return c.json({ error: 'unauthorized' }, 401);
124
+ const raw = c.req.raw.body;
125
+ if (!raw)
126
+ return c.json({ error: 'empty body' }, 400);
127
+ const body = guardSingleUpload(c, raw);
128
+ if (body instanceof Response)
129
+ return body;
130
+ try {
131
+ const file = await catalogue.replaceContent(ctx, c.req.param('id'), {
132
+ name: c.req.header('x-filename') || undefined,
133
+ mime: c.req.header('content-type') ?? 'application/octet-stream',
134
+ body,
135
+ });
136
+ if (!file)
137
+ return c.json({ error: 'not found' }, 404);
138
+ return c.json({ file });
139
+ }
140
+ catch (e) {
141
+ if (isUploadTooLarge(e))
142
+ return c.json({ error: SINGLE_UPLOAD_TOO_LARGE }, 413);
143
+ return c.json({ error: e instanceof Error ? e.message : 'invalid' }, 400);
144
+ }
145
+ });
146
+ }
147
+ // PUT a PRIVATE upload → catalogue.ingestPrivate under a set key derived from the request:
148
+ // either an UNLOCK TOKEN (an already-unlocked session — same mechanism the content-key route
149
+ // uses) or a password header (deniable) / neither (standard server key).
150
+ const pv = opts.privateUpload;
151
+ if (pv && catalogue) {
152
+ const passwordHeader = pv.passwordHeader ?? 'x-private-password';
153
+ const tokenHeader = pv.tokenHeader ?? 'x-unlock-token';
154
+ app.put(`${base}/upload-private`, async (c) => {
155
+ const who = await pv.authorize(c);
156
+ if (!who)
157
+ return c.json({ error: 'unauthorized' }, 401);
158
+ const raw = c.req.raw.body;
159
+ if (!raw)
160
+ return c.json({ error: 'empty body' }, 400);
161
+ const body = guardSingleUpload(c, raw);
162
+ if (body instanceof Response)
163
+ return body;
164
+ let setKey;
165
+ let keyMode;
166
+ const unlockToken = c.req.header(tokenHeader) || undefined;
167
+ if (unlockToken) {
168
+ // Token path: recover the sealed set key + mode from the grant. The token must belong
169
+ // to the session caller.
170
+ const grant = await pv.unlock.verify(unlockToken);
171
+ if (!grant || grant.userId !== who.userId) {
172
+ return c.json({ error: 'invalid unlock token' }, 401);
173
+ }
174
+ setKey = grant.setKey;
175
+ keyMode = grant.mode;
176
+ }
177
+ else {
178
+ // Password path: derive fresh from the header (deniable) or the server key. Under the
179
+ // gate policy the password is an access check only — verify, then wrap standard.
180
+ const password = c.req.header(passwordHeader) || undefined;
181
+ const policy = pv.passwordPolicy ?? { mode: 'derive' };
182
+ let derivePassword = password;
183
+ if (policy.mode === 'gate' && password) {
184
+ if (!(await policy.verifyPassword(who, password))) {
185
+ return c.json({ error: 'invalid password' }, 401);
186
+ }
187
+ derivePassword = undefined;
188
+ }
189
+ setKey = pv.unlock.deriveSetKey({
190
+ userId: who.userId,
191
+ userSalt: who.userSalt,
192
+ password: derivePassword,
193
+ });
194
+ keyMode = derivePassword ? 'deniable' : 'standard';
195
+ }
196
+ // The private owner axis is the userId (deniability is personal), matching mountPrivateRoutes.
197
+ const ctx = { userId: who.userId, tenantId: who.userId };
198
+ const tags = c.req.header('x-tags');
199
+ try {
200
+ const item = await catalogue.ingestPrivate(ctx, {
201
+ name: c.req.header('x-filename') ?? 'upload',
202
+ mime: c.req.header('content-type') ?? 'application/octet-stream',
203
+ body,
204
+ tags: tags
205
+ ? tags
206
+ .split(',')
207
+ .map((t) => t.trim())
208
+ .filter(Boolean)
209
+ : undefined,
210
+ }, { setKey, keyMode });
211
+ return c.json({ item });
212
+ }
213
+ catch (e) {
214
+ if (isUploadTooLarge(e))
215
+ return c.json({ error: SINGLE_UPLOAD_TOO_LARGE }, 413);
216
+ throw e;
217
+ }
218
+ });
219
+ }
220
+ // Chunked upload (large files, direct-to-backend). init → the client PUTs each chunk straight to
221
+ // the backend (URLs the app minted) → complete registers the row over the assembled bytes. The
222
+ // bytes never traverse this origin.
223
+ const chunked = opts.chunked;
224
+ if (chunked && opts.authorizeUpload) {
225
+ app.post(`${base}/upload-init`, async (c) => {
226
+ const ctx = await opts.authorizeUpload?.(c);
227
+ if (!ctx)
228
+ return c.json({ error: 'unauthorized' }, 401);
229
+ const body = (await c.req.json().catch(() => ({})));
230
+ if (!body.size || body.size <= 0)
231
+ return c.json({ error: 'size required' }, 400);
232
+ const init = await chunked.init(ctx, {
233
+ name: body.name ?? 'upload',
234
+ mime: body.mime ?? 'application/octet-stream',
235
+ size: body.size,
236
+ tags: body.tags,
237
+ });
238
+ return c.json(init);
239
+ });
240
+ app.post(`${base}/upload-complete`, async (c) => {
241
+ const ctx = await opts.authorizeUpload?.(c);
242
+ if (!ctx)
243
+ return c.json({ error: 'unauthorized' }, 401);
244
+ const body = (await c.req.json().catch(() => ({})));
245
+ if (!body.sid)
246
+ return c.json({ error: 'sid required' }, 400);
247
+ const file = await chunked.complete(body.sid, { size: body.size });
248
+ if (!file)
249
+ return c.json({ error: 'unknown session' }, 404);
250
+ return c.json({ file });
251
+ });
252
+ app.post(`${base}/upload-abort`, async (c) => {
253
+ const ctx = await opts.authorizeUpload?.(c);
254
+ if (!ctx)
255
+ return c.json({ error: 'unauthorized' }, 401);
256
+ const body = (await c.req.json().catch(() => ({})));
257
+ if (body.sid)
258
+ chunked.abort(body.sid);
259
+ return c.json({ ok: true });
260
+ });
261
+ }
262
+ // ── Listing + collection routes ───────────────────────────────────────────────
263
+ // All require authorizeUpload (session auth) and a catalogue. They follow the same
264
+ // auth/ctx/envelope pattern as the upload routes above. The `includeUnlockable` gate is the
265
+ // caller's responsibility — the route passes the query param through, and it's the app's session
266
+ // middleware that decides whether the caller has unlocked.
267
+ if (opts.authorizeUpload && catalogue) {
268
+ // GET /_files/list → cursor-paginated catalogue listing. Query params mirror
269
+ // ListFilesOptions: type, tag, query (q), collectionId, favorite, includeUnlockable,
270
+ // sort (recent|oldest|name|size|random|lastViewed), cursor, limit.
271
+ app.get(`${base}/list`, async (c) => {
272
+ const ctx = await opts.authorizeUpload?.(c);
273
+ if (!ctx)
274
+ return c.json({ error: 'unauthorized' }, 401);
275
+ const q = c.req.query();
276
+ const sort = FILE_SORTS.includes(q.sort ?? '')
277
+ ? q.sort
278
+ : undefined;
279
+ const limit = q.limit ? Number(q.limit) : undefined;
280
+ const page = await catalogue.list(ctx, {
281
+ type: q.type,
282
+ tag: q.tag || undefined,
283
+ query: q.q || undefined,
284
+ collectionId: q.collectionId || undefined,
285
+ favorite: q.favorite === '1' ? true : undefined,
286
+ includeUnlockable: q.includeUnlockable === '1' ? true : undefined,
287
+ sort,
288
+ cursor: q.cursor || undefined,
289
+ limit: limit && Number.isFinite(limit) ? limit : undefined,
290
+ });
291
+ return c.json(page);
292
+ });
293
+ // GET /_files/collections → list all visible collections
294
+ app.get(`${base}/collections`, async (c) => {
295
+ const ctx = await opts.authorizeUpload?.(c);
296
+ if (!ctx)
297
+ return c.json({ error: 'unauthorized' }, 401);
298
+ const includeUnlockable = c.req.query('includeUnlockable') === '1';
299
+ const collections = await catalogue.listCollections(ctx, { includeUnlockable });
300
+ return c.json({ collections });
301
+ });
302
+ // POST /_files/collections → create a collection
303
+ app.post(`${base}/collections`, async (c) => {
304
+ const ctx = await opts.authorizeUpload?.(c);
305
+ if (!ctx)
306
+ return c.json({ error: 'unauthorized' }, 401);
307
+ const body = (await c.req.json().catch(() => ({})));
308
+ if (!body.name)
309
+ return c.json({ error: 'name required' }, 400);
310
+ try {
311
+ const collection = await catalogue.createCollection(ctx, {
312
+ name: body.name,
313
+ visibility: body.visibility === 'unlock' || body.visibility === 'normal'
314
+ ? body.visibility
315
+ : undefined,
316
+ });
317
+ return c.json({ collection }, 201);
318
+ }
319
+ catch (e) {
320
+ return c.json({ error: e instanceof Error ? e.message : 'invalid' }, 400);
321
+ }
322
+ });
323
+ // PATCH /_files/collections/:id → rename / change visibility
324
+ app.patch(`${base}/collections/:id`, async (c) => {
325
+ const ctx = await opts.authorizeUpload?.(c);
326
+ if (!ctx)
327
+ return c.json({ error: 'unauthorized' }, 401);
328
+ const body = (await c.req.json().catch(() => ({})));
329
+ try {
330
+ const collection = await catalogue.updateCollection(ctx, c.req.param('id'), {
331
+ name: body.name,
332
+ visibility: body.visibility === 'unlock' || body.visibility === 'normal'
333
+ ? body.visibility
334
+ : undefined,
335
+ });
336
+ if (!collection)
337
+ return c.json({ error: 'not found' }, 404);
338
+ return c.json({ collection });
339
+ }
340
+ catch (e) {
341
+ return c.json({ error: e instanceof Error ? e.message : 'invalid' }, 400);
342
+ }
343
+ });
344
+ // DELETE /_files/collections/:id → remove collection (not files)
345
+ app.delete(`${base}/collections/:id`, async (c) => {
346
+ const ctx = await opts.authorizeUpload?.(c);
347
+ if (!ctx)
348
+ return c.json({ error: 'unauthorized' }, 401);
349
+ try {
350
+ await catalogue.removeCollection(ctx, c.req.param('id'));
351
+ return c.json({ ok: true });
352
+ }
353
+ catch (e) {
354
+ return c.json({ error: e instanceof Error ? e.message : 'invalid' }, 400);
355
+ }
356
+ });
357
+ // POST /_files/collections/:id/members → add files to a collection
358
+ app.post(`${base}/collections/:id/members`, async (c) => {
359
+ const ctx = await opts.authorizeUpload?.(c);
360
+ if (!ctx)
361
+ return c.json({ error: 'unauthorized' }, 401);
362
+ const body = (await c.req.json().catch(() => ({})));
363
+ if (!Array.isArray(body.fileIds) || body.fileIds.length === 0) {
364
+ return c.json({ error: 'fileIds required' }, 400);
365
+ }
366
+ await catalogue.addToCollection(ctx, c.req.param('id'), body.fileIds);
367
+ return c.json({ ok: true });
368
+ });
369
+ // DELETE /_files/collections/:id/members → remove files from a collection
370
+ app.delete(`${base}/collections/:id/members`, async (c) => {
371
+ const ctx = await opts.authorizeUpload?.(c);
372
+ if (!ctx)
373
+ return c.json({ error: 'unauthorized' }, 401);
374
+ const body = (await c.req.json().catch(() => ({})));
375
+ if (!Array.isArray(body.fileIds) || body.fileIds.length === 0) {
376
+ return c.json({ error: 'fileIds required' }, 400);
377
+ }
378
+ await catalogue.removeFromCollection(ctx, c.req.param('id'), body.fileIds);
379
+ return c.json({ ok: true });
380
+ });
381
+ // POST /_files/favorites → toggle favorite state for a single file
382
+ app.post(`${base}/favorites`, async (c) => {
383
+ const ctx = await opts.authorizeUpload?.(c);
384
+ if (!ctx)
385
+ return c.json({ error: 'unauthorized' }, 401);
386
+ const body = (await c.req.json().catch(() => ({})));
387
+ if (!body.fileId)
388
+ return c.json({ error: 'fileId required' }, 400);
389
+ await catalogue.setFavorite(ctx, body.fileId, body.favorite ?? true);
390
+ return c.json({ ok: true });
391
+ });
392
+ // POST /_files/view → record a watch-open of a file (feeds the `lastViewed` sort). Idempotent
393
+ // in effect (upsert); an unknown/other-owner id records nothing but still returns ok.
394
+ app.post(`${base}/view`, async (c) => {
395
+ const ctx = await opts.authorizeUpload?.(c);
396
+ if (!ctx)
397
+ return c.json({ error: 'unauthorized' }, 401);
398
+ const body = (await c.req.json().catch(() => ({})));
399
+ if (!body.fileId)
400
+ return c.json({ error: 'fileId required' }, 400);
401
+ await catalogue.recordView(ctx, body.fileId);
402
+ return c.json({ ok: true });
403
+ });
404
+ }
405
+ }
@@ -0,0 +1,9 @@
1
+ export { createCatalogueMediaSource, createCataloguePhotoSource, createCatalogueVideoSource, createFileToPlayable, } from './adapter';
2
+ export { catalogueUploadKey, createSqliteFileCatalogue, fileBinaryKey, type ImageDerivatives, type ImageDerivativesOptions, type SqliteFileCatalogueOptions, type VideoProcessor, } from './catalogue';
3
+ export type { CatalogueStats } from './types';
4
+ export { type ChunkedInit, type ChunkedUploadService, createChunkedUploadService, type MintChunkUpload, } from './chunkedUpload';
5
+ export { createFileUrls, type FileUrls, type MountFileRoutesOptions, mountFileRoutes, SINGLE_UPLOAD_TOO_LARGE, } from './fileRoutes';
6
+ export { FILE_CATALOGUE_MIGRATIONS } from './migrations';
7
+ export { createFilesStack, type FilesStack, type FilesStackMountAuth, type FilesStackOptions, } from './stack';
8
+ export { createLedgeredDeletes, createStorageOpsLedger, createStorageReconciler, type StorageAction, type StorageOpRow, type StorageOpsLedger, type StorageReconciler, } from './storageOps';
9
+ export { type CollectionKind, type CollectionVisibility, classifyFileType, FILE_SORTS, type FileCatalogue, type FileCollection, type FilePage, type FileRecord, type FileSort, type FileToPlayable, type FileType, type ListFilesOptions, type PrivateFileCatalogue, type PrivateFileItem, type PrivateKeyMode, type TagFacet, } from './types';
@@ -0,0 +1,11 @@
1
+ // cursedbelt-server/storage FileCatalogue — a SQLite-backed tagged flat file system over the
2
+ // MediaStore + media pipeline + private-media. One source of truth for a user's binaries; the
3
+ // video gallery is a video-filtered view over it.
4
+ export { createCatalogueMediaSource, createCataloguePhotoSource, createCatalogueVideoSource, createFileToPlayable, } from './adapter';
5
+ export { catalogueUploadKey, createSqliteFileCatalogue, fileBinaryKey, } from './catalogue';
6
+ export { createChunkedUploadService, } from './chunkedUpload';
7
+ export { createFileUrls, mountFileRoutes, SINGLE_UPLOAD_TOO_LARGE, } from './fileRoutes';
8
+ export { FILE_CATALOGUE_MIGRATIONS } from './migrations';
9
+ export { createFilesStack, } from './stack';
10
+ export { createLedgeredDeletes, createStorageOpsLedger, createStorageReconciler, } from './storageOps';
11
+ export { classifyFileType, FILE_SORTS, } from './types';
@@ -0,0 +1,2 @@
1
+ import type { Migration } from '../../migration/types';
2
+ export declare const FILE_CATALOGUE_MIGRATIONS: readonly Migration[];