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,261 @@
1
+ import type { PlayableVideo, ProcessingStatus } from 'cursedbelt-core/media';
2
+ import type { PrivateIndex } from '../../private-media/index-store';
3
+ import type { MediaCtx } from '../types';
4
+ export type FileType = 'image' | 'video' | 'audio' | 'doc' | 'code' | 'other';
5
+ /** User-defined groupings (Apple-Photos-style). `favorites` is a system singleton per owner;
6
+ * `custom` are free-form. */
7
+ export type CollectionKind = 'custom' | 'favorites';
8
+ /** `normal` — always visible; `unlock` — hidden unless caller passes `includeUnlockable: true`. */
9
+ export type CollectionVisibility = 'normal' | 'unlock';
10
+ export type FileCollection = {
11
+ id: string;
12
+ name: string;
13
+ kind: CollectionKind;
14
+ visibility: CollectionVisibility;
15
+ /** Live count of member files. */
16
+ fileCount: number;
17
+ createdAt: number;
18
+ updatedAt: number;
19
+ };
20
+ /** The clean, UI-facing record — explicit capability booleans, no storage keys leaked (ca's
21
+ * worst inconsistency was `thumbnailKey`-vs-`hasThumbnail` across two shapes). */
22
+ export type FileRecord = {
23
+ id: string;
24
+ name: string;
25
+ mime: string;
26
+ size: number;
27
+ fileType: FileType;
28
+ tags: string[];
29
+ isPrivate: boolean;
30
+ hasThumbnail: boolean;
31
+ hasHls: boolean;
32
+ hasSprites: boolean;
33
+ durationSeconds?: number;
34
+ processingStatus: ProcessingStatus;
35
+ processingProgress: number;
36
+ /** True when this file is a member of the caller's Favorites collection. */
37
+ favorite: boolean;
38
+ /** All collection ids this file belongs to (within the owner's scope). */
39
+ collectionIds: string[];
40
+ createdAt: number;
41
+ updatedAt: number;
42
+ /** Set once `trash()` soft-deletes this file (epoch ms) — cleared by `restore()`. Undefined for
43
+ * a live (non-trashed) file. */
44
+ trashedAt?: number;
45
+ };
46
+ /** A tag with how many files carry it — the facet the flat browser renders as filter chips. */
47
+ export type TagFacet = {
48
+ tag: string;
49
+ count: number;
50
+ };
51
+ /** A page of results with an opaque cursor for the next page (cursor-keyed, not OFFSET). */
52
+ export type FilePage = {
53
+ items: FileRecord[];
54
+ nextCursor?: string;
55
+ };
56
+ /** Every listing order the catalogue understands — the single source of truth the route layer
57
+ * validates against and the `FileSort` union derives from. `recent` = newest first (default);
58
+ * `oldest` = oldest first; `name` = A→Z; `size` = largest first; `random` = a seeded shuffle
59
+ * (the seed is minted per session and folded into the cursor, so a visit reshuffles but its own
60
+ * pagination is stable); `lastViewed` = most-recently-opened first (never-viewed files last). */
61
+ export declare const FILE_SORTS: readonly ["recent", "oldest", "name", "size", "random", "lastViewed"];
62
+ /** Listing order. Every sort is cursor-paginated (the cursor encodes the sort key + id tie-break,
63
+ * plus the shuffle seed for `random`). A cursor is only valid under the sort that minted it. */
64
+ export type FileSort = (typeof FILE_SORTS)[number];
65
+ export type ListFilesOptions = {
66
+ /** Smart-view filter (indexed). */
67
+ type?: FileType;
68
+ /** Single-tag filter (indexed via the join table). */
69
+ tag?: string;
70
+ /** Name substring (public files only — private names are sealed). */
71
+ query?: string;
72
+ /** Include the caller's private files (requires an unlocked set — the caller resolves which
73
+ * ones actually open). Omitted ⇒ public only, matching ca's default listing. */
74
+ includePrivate?: boolean;
75
+ /** Filter to members of a specific collection. An unlock-gated collection yields empty unless
76
+ * `includeUnlockable` is also true. */
77
+ collectionId?: string;
78
+ /** Filter to files that are favorited (i.e. members of the owner's Favorites collection). */
79
+ favorite?: boolean;
80
+ /** Expose unlock-gated collections/results. Default false — the route layer gates this. */
81
+ includeUnlockable?: boolean;
82
+ /** Listing order (default 'recent'). A cursor is only valid for the sort it was minted under. */
83
+ sort?: FileSort;
84
+ cursor?: string;
85
+ limit?: number;
86
+ /** Browse the trash bin instead of the live library: true ⇒ ONLY trashed rows, omitted/false ⇒
87
+ * ONLY live rows (the default — trashed files never appear in a normal listing). */
88
+ trashedOnly?: boolean;
89
+ };
90
+ /** Server-authoritative smart-view classification, computed ONCE at write time and stored.
91
+ * `isText` (a non-binary inline file) ⇒ code; else by mime. */
92
+ export declare function classifyFileType(mime: string, isText?: boolean): FileType;
93
+ /** The catalogue contract. A SQLite implementation is the production default; the interface lets
94
+ * an app swap it. All calls are owner-scoped via {@link MediaCtx} (one tenant never sees another). */
95
+ /** Cheap owner-scoped aggregates (COUNT/SUM over the files table) — the admin
96
+ * surface's "site stats" source. Private items report count + bytes only
97
+ * (names/kinds stay sealed). */
98
+ export type CatalogueStats = {
99
+ files: number;
100
+ bytes: number;
101
+ byType: Partial<Record<FileType, number>>;
102
+ privateItems: number;
103
+ privateBytes: number;
104
+ processing: number;
105
+ failed: number;
106
+ };
107
+ export interface FileCatalogue {
108
+ list(ctx: MediaCtx, opts?: ListFilesOptions): Promise<FilePage>;
109
+ get(ctx: MediaCtx, id: string): Promise<FileRecord | null>;
110
+ /** Owner-scoped aggregates for admin/stats surfaces. */
111
+ stats(ctx: MediaCtx): Promise<CatalogueStats>;
112
+ tagFacets(ctx: MediaCtx, opts?: {
113
+ includePrivate?: boolean;
114
+ }): Promise<TagFacet[]>;
115
+ /** Ingest a public upload: persist bytes, classify, kick off processing (thumbnail/HLS). */
116
+ ingest(ctx: MediaCtx, upload: {
117
+ name: string;
118
+ mime: string;
119
+ body: ReadableStream<Uint8Array>;
120
+ size?: number;
121
+ tags?: string[];
122
+ }): Promise<FileRecord>;
123
+ /** Register bytes uploaded OUT OF BAND (a chunked upload sent direct to the backend) — insert the
124
+ * row + kick processing WITHOUT re-storing bytes (they already sit at the catalogue's key for this
125
+ * id: fsSourceKey for video, fileBinaryKey otherwise). */
126
+ registerUploaded(ctx: MediaCtx, upload: {
127
+ id: string;
128
+ name: string;
129
+ mime: string;
130
+ size: number;
131
+ tags?: string[];
132
+ }): Promise<FileRecord>;
133
+ /** Rename / retag a file. */
134
+ update(ctx: MediaCtx, id: string, patch: {
135
+ name?: string;
136
+ tags?: string[];
137
+ }): Promise<FileRecord | null>;
138
+ /** Overwrite a file's BYTES in place — the identity (id, tags, collections, favorites, view
139
+ * history) survives while mime/size/fileType re-derive, stale derivatives are wiped, and
140
+ * processing re-kicks (video transcode / image thumbnail). Public files only — replacing an
141
+ * encrypted private item throws (delete + re-upload instead). Returns the refreshed record,
142
+ * or null if the id is unknown/other-owner. */
143
+ replaceContent(ctx: MediaCtx, id: string, upload: {
144
+ mime: string;
145
+ body: ReadableStream<Uint8Array>;
146
+ size?: number;
147
+ name?: string;
148
+ }): Promise<FileRecord | null>;
149
+ /** PERMANENTLY delete a file + all its derivatives + tags + collection memberships. This is the
150
+ * hard-delete — a deliberate second action, typically only called on an already-trashed row
151
+ * (see {@link trash}). No-ops (never throws) for an unknown or other-owner id. */
152
+ remove(ctx: MediaCtx, id: string): Promise<void>;
153
+ /** Soft-delete: marks the file trashed (hidden from normal `list()`) without touching its bytes
154
+ * or metadata — recoverable via {@link restore}. The PRIMARY delete action a user takes.
155
+ * No-ops for an unknown/other-owner id or one already trashed. */
156
+ trash(ctx: MediaCtx, id: string): Promise<void>;
157
+ /** Undo `trash()`: clears trashed_at so the file reappears in normal listings. No-ops for an
158
+ * unknown/other-owner id. */
159
+ restore(ctx: MediaCtx, id: string): Promise<void>;
160
+ /** Ingest file(s) from a LOCAL path already on this host's disk (a directory imports every
161
+ * regular file inside it, recursively; a file imports just itself). Each imported file is
162
+ * tagged with its immediate containing folder name (the catalogue is flat — folder structure
163
+ * survives as a tag, not a tree) plus any `tags` passed. Unreadable/unsupported entries are
164
+ * skipped, not thrown — the return value reports both. */
165
+ importFromPath(ctx: MediaCtx, absPath: string, opts?: {
166
+ tags?: string[];
167
+ }): Promise<{
168
+ imported: FileRecord[];
169
+ skipped: {
170
+ path: string;
171
+ reason: string;
172
+ }[];
173
+ }>;
174
+ /** Recover from a restart that lost in-flight transcodes: re-kick public video rows still marked
175
+ * pending/processing (their source persists), and mark stuck private rows failed. Call once at
176
+ * app startup. Returns how many were re-queued vs. failed. */
177
+ recoverProcessing(): {
178
+ requeued: number;
179
+ failed: number;
180
+ };
181
+ /** Backfill grid thumbnails for finished public video rows that have no poster (landed while
182
+ * transcoding was off, or failed before the poster step). Re-kicks the poster pipeline for each.
183
+ * Returns how many were re-queued. */
184
+ backfillVideoPosters(): {
185
+ requeued: number;
186
+ };
187
+ /** Backfill grid thumbnails for public image rows with no poster (e.g. HEIC photos whose sharp
188
+ * thumbnail failed before the ffmpeg fallback). Re-kicks the image pipeline. */
189
+ backfillImageThumbnails(): {
190
+ requeued: number;
191
+ };
192
+ /** List the owner's collections. Unlock-gated ones are excluded unless `includeUnlockable`. */
193
+ listCollections(ctx: MediaCtx, opts?: {
194
+ includeUnlockable?: boolean;
195
+ }): Promise<FileCollection[]>;
196
+ /** Create a custom collection. Name is trimmed; empty names throw. Duplicate names are allowed. */
197
+ createCollection(ctx: MediaCtx, input: {
198
+ name: string;
199
+ visibility?: CollectionVisibility;
200
+ }): Promise<FileCollection>;
201
+ /** Rename or change visibility of an owned collection. Returns null if not found. */
202
+ updateCollection(ctx: MediaCtx, id: string, patch: {
203
+ name?: string;
204
+ visibility?: CollectionVisibility;
205
+ }): Promise<FileCollection | null>;
206
+ /** Delete a custom collection (and its memberships). Never deletes files. Favorites cannot be
207
+ * removed — throws an Error. */
208
+ removeCollection(ctx: MediaCtx, id: string): Promise<void>;
209
+ /** Add files to a collection. Idempotent (INSERT OR IGNORE). Unknown or other-owner file ids are
210
+ * silently skipped. */
211
+ addToCollection(ctx: MediaCtx, collectionId: string, fileIds: string[]): Promise<void>;
212
+ /** Remove files from a collection. Silently ignores ids not in the collection. */
213
+ removeFromCollection(ctx: MediaCtx, collectionId: string, fileIds: string[]): Promise<void>;
214
+ /** Toggle the favorite state of a file. Lazily creates the owner's Favorites collection on first
215
+ * use. */
216
+ setFavorite(ctx: MediaCtx, fileId: string, favorite: boolean): Promise<void>;
217
+ /** Record a view/watch-open of a file (upserts its last-viewed timestamp + bumps a count) — the
218
+ * signal the `lastViewed` sort orders by. No-ops for an unknown or other-owner file id. */
219
+ recordView(ctx: MediaCtx, fileId: string): Promise<void>;
220
+ }
221
+ /** Resolve a video FileRecord to a PlayableVideo (URLs baked server-side). Non-video ⇒ null. */
222
+ export type FileToPlayable = (ctx: MediaCtx, file: FileRecord) => Promise<PlayableVideo | null>;
223
+ /** How a private item's content key is wrapped: `standard` (server-recoverable) vs `deniable`
224
+ * (password-derived; zero-knowledge). Mirrors private-media `KeyMode`. */
225
+ export type PrivateKeyMode = 'standard' | 'deniable';
226
+ /** The OPAQUE locked-index row — only non-sensitive metadata (so a user can prune media whose
227
+ * password is lost). NEVER name/tags/thumbnail: those are sealed. No set identifier is exposed. */
228
+ export type PrivateFileItem = {
229
+ id: string;
230
+ size: number;
231
+ createdAt: number;
232
+ processingStatus: ProcessingStatus;
233
+ processingProgress: number;
234
+ hasHls: boolean;
235
+ };
236
+ /** Private-media capability of a catalogue. `createSqliteFileCatalogue` implements this alongside
237
+ * {@link FileCatalogue}, storing private items as sealed rows in the SAME table (one source of
238
+ * truth). Membership is by decrypt-success — no stored password/set hash.
239
+ *
240
+ * Deliberately small: the catalogue owns only INGEST (the new private-upload path). Everything
241
+ * else — the locked index, unlock, try-a-password reveal, the gated content-key route, and the
242
+ * encrypted-HLS byte serving — is the existing `mountPrivateRoutes`, which operates through a
243
+ * {@link PrivateIndex}. `asPrivateIndex()` is the view that lets those routes read/write the
244
+ * catalogue's private rows, so there is exactly one store and one set of routes. */
245
+ export interface PrivateFileCatalogue {
246
+ /** Ingest an encrypted video: stage plaintext (never persisted), transcode to AES-128 HLS, seal
247
+ * the descriptor + wrap the content key under `setKey`, and insert a private row. */
248
+ ingestPrivate(ctx: MediaCtx, upload: {
249
+ name: string;
250
+ mime: string;
251
+ body: ReadableStream<Uint8Array>;
252
+ size?: number;
253
+ tags?: string[];
254
+ }, opts: {
255
+ setKey: Buffer;
256
+ keyMode: PrivateKeyMode;
257
+ }): Promise<PrivateFileItem>;
258
+ /** A {@link PrivateIndex} view over this catalogue's private rows — pass to `mountPrivateRoutes`
259
+ * so unlock/index/reveal/key/HLS all operate on the catalogue. `remove` also drops the bytes. */
260
+ asPrivateIndex(): PrivateIndex;
261
+ }
@@ -0,0 +1,22 @@
1
+ /** Every listing order the catalogue understands — the single source of truth the route layer
2
+ * validates against and the `FileSort` union derives from. `recent` = newest first (default);
3
+ * `oldest` = oldest first; `name` = A→Z; `size` = largest first; `random` = a seeded shuffle
4
+ * (the seed is minted per session and folded into the cursor, so a visit reshuffles but its own
5
+ * pagination is stable); `lastViewed` = most-recently-opened first (never-viewed files last). */
6
+ export const FILE_SORTS = ['recent', 'oldest', 'name', 'size', 'random', 'lastViewed'];
7
+ const DOC_MIME = /pdf|word|excel|spreadsheet|presentation|csv|officedocument|rtf|epub|^text\//;
8
+ /** Server-authoritative smart-view classification, computed ONCE at write time and stored.
9
+ * `isText` (a non-binary inline file) ⇒ code; else by mime. */
10
+ export function classifyFileType(mime, isText = false) {
11
+ if (mime.startsWith('image/'))
12
+ return 'image';
13
+ if (mime.startsWith('video/'))
14
+ return 'video';
15
+ if (mime.startsWith('audio/'))
16
+ return 'audio';
17
+ if (isText)
18
+ return 'code';
19
+ if (DOC_MIME.test(mime))
20
+ return 'doc';
21
+ return 'other';
22
+ }
@@ -0,0 +1,3 @@
1
+ /** True when a browser can render this image mime inline (no JPEG derivative needed). */
2
+ export declare function isBrowserRenderableImage(mime: string): boolean;
3
+ export declare function ffmpegImageThumbnail(bytes: Uint8Array, workDir: string, id: string, maxWidth?: number): Promise<Uint8Array | null>;
@@ -0,0 +1,50 @@
1
+ import { mkdir, rm } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { imageThumbnailArgs, runFfmpegProgress } from '../media-bun';
4
+ // Image mimes browsers CANNOT render inline (so the full-view <img> needs a JPEG derivative, not
5
+ // the original). Chrome/Firefox reject HEIC/HEIF; the rest (jpeg/png/webp/gif/avif/bmp/svg) render
6
+ // natively. Safari can show HEIC, but we normalize for everyone.
7
+ const BROWSER_INCOMPATIBLE_IMAGE_MIMES = new Set([
8
+ 'image/heic',
9
+ 'image/heif',
10
+ 'image/heic-sequence',
11
+ 'image/heif-sequence',
12
+ ]);
13
+ /** True when a browser can render this image mime inline (no JPEG derivative needed). */
14
+ export function isBrowserRenderableImage(mime) {
15
+ return mime.startsWith('image/') && !BROWSER_INCOMPATIBLE_IMAGE_MIMES.has(mime.toLowerCase());
16
+ }
17
+ // Produce a width-capped JPEG thumbnail from arbitrary image bytes using ffmpeg — the fallback for
18
+ // formats sharp's prebuilt binary can't decode (notably HEIC/HEVC iPhone photos: the licensed-out
19
+ // libheif build only accepts AVIF). Best-effort: returns null on any failure. Uses a temp file
20
+ // because ffmpeg's HEIF demuxer needs seekable input (a pipe won't do).
21
+ export async function ffmpegImageThumbnail(bytes, workDir, id, maxWidth = 512) {
22
+ const dir = path.join(workDir, '_imgthumb', id);
23
+ try {
24
+ await mkdir(dir, { recursive: true });
25
+ const inPath = path.join(dir, 'in.bin');
26
+ await Bun.write(inPath, bytes);
27
+ const outPath = path.join(dir, 'out.jpg');
28
+ let stderrTail = '';
29
+ const ok = await runFfmpegProgress(imageThumbnailArgs(inPath, outPath, maxWidth), 0, undefined, (tail) => {
30
+ stderrTail = tail;
31
+ });
32
+ if (!ok) {
33
+ // The "undecodable bytes" fixture test deliberately hits this path too — tagged
34
+ // `[expected-in-tests]` (grep-filterable) rather than silenced, so a real decode
35
+ // regression still surfaces.
36
+ console.warn(`[imageThumbnail][expected-in-tests] ffmpeg decode failed: ${stderrTail.slice(-400)}`);
37
+ return null;
38
+ }
39
+ const out = Bun.file(outPath);
40
+ if (!(await out.exists()))
41
+ return null;
42
+ return new Uint8Array(await out.arrayBuffer());
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ finally {
48
+ await rm(dir, { recursive: true, force: true }).catch(() => { });
49
+ }
50
+ }
@@ -0,0 +1,15 @@
1
+ export { DERIVATIVE_SUFFIXES, hlsMasterKeyFor, hlsPrefixFor, isDerivativeKey, posterKeyFor, spriteKeyFor, spriteVttKeyFor, webPlayableKeyFor, } from './videoDerivativeKeys';
2
+ export type { BinaryServerConfig, FileTokenClaims, FileTokenSigner } from './binaryServerStore';
3
+ export { createBinaryServerStore } from './binaryServerStore';
4
+ export { type BinaryServerProcessingConfig, createBinaryServerProcessing, } from './binaryServerProcessing';
5
+ export { createTranscodeJobClient, type TranscodeJobClient, type TranscodeJobClientConfig, type TranscodeJobResult, type TranscodeJobStatus, type TranscodeJobStatusBody, } from './transcodeJobs';
6
+ export { createDiskStore } from './diskStore';
7
+ export { type CatalogueStats, type ChunkedInit, type ChunkedUploadService, catalogueUploadKey, classifyFileType, createCatalogueMediaSource, createCataloguePhotoSource, createCatalogueVideoSource, createChunkedUploadService, createFilesStack, createFileToPlayable, createFileUrls, createLedgeredDeletes, createSqliteFileCatalogue, createStorageOpsLedger, createStorageReconciler, FILE_CATALOGUE_MIGRATIONS, type FileCatalogue, type FilePage, type FileRecord, type FilesStack, type FilesStackMountAuth, type FilesStackOptions, type FileToPlayable, type FileType, type FileUrls, fileBinaryKey, type ImageDerivatives, type ImageDerivativesOptions, type ListFilesOptions, type MintChunkUpload, type MountFileRoutesOptions, mountFileRoutes, type PrivateFileCatalogue, type PrivateFileItem, type PrivateKeyMode, type SqliteFileCatalogueOptions, type StorageAction, type StorageOpRow, type StorageOpsLedger, type StorageReconciler, type VideoProcessor, } from './files';
8
+ export { type CorsResolver, type MountMediaOptions, mountMediaRoutes } from './mountMediaRoutes';
9
+ export { createDiskSource, createMediaUrls, type DiskSource, type MediaUrls, SourceRegistry, } from './sources';
10
+ export { createS3ObjectStore, type S3ClientLike, type S3ObjectHandle, type S3ObjectStoreOptions, type S3ObjectWriter, } from './s3ObjectStore';
11
+ export { decodeRange, type RangeDecision, type ServeRangeOpts, serveRange } from './streamRange';
12
+ export { createStreamTokenService, type StreamGrant, type StreamTokenService } from './streamToken';
13
+ export { fsHlsFileKey, fsHlsMasterKey, fsHlsPrefix, fsMp4Key, fsPosterKey, fsSourceKey, fsSpriteKey, fsVttKey, type MediaCtx, type MediaGallerySource, type MediaSource, type MediaStore, type PhotoSource, privateFsKey, } from './types';
14
+ export { type ProcessOptions, type ProcessResult, processVideo, } from './videoProcessor';
15
+ export { objectStoreAsMediaStore } from './objectStoreMediaStore';
@@ -0,0 +1,27 @@
1
+ // cursedbelt-server/storage — byte-storage layer for the media pipeline: MediaStore adapters
2
+ // (disk / binary-server), a Range→Response helper, path-embedded stream tokens, a source
3
+ // registry, and the ingest→derivatives orchestrator. Builds on `server/media` (codec work) +
4
+ // `server/auth` (jose tokens). The `PlayableVideo` contract lives in `cursedbelt-core/media`.
5
+ // The derivative NAME contract — the seam between binary-server (which writes these names),
6
+ // satellite-kit (which re-exports them for the satellite apps) and this package's own token
7
+ // minting. Pure string transforms: nothing here can resolve a stored object.
8
+ export { DERIVATIVE_SUFFIXES, hlsMasterKeyFor, hlsPrefixFor, isDerivativeKey, posterKeyFor, spriteKeyFor, spriteVttKeyFor, webPlayableKeyFor, } from './videoDerivativeKeys';
9
+ export { createBinaryServerStore } from './binaryServerStore';
10
+ export { createBinaryServerProcessing, } from './binaryServerProcessing';
11
+ // The transcode API as a plain job client, for apps whose binaries do NOT live in the file
12
+ // catalogue (apps/notes keys attachments by sha256). One derivative pipeline, two doors.
13
+ export { createTranscodeJobClient, } from './transcodeJobs';
14
+ export { createDiskStore } from './diskStore';
15
+ // The FileCatalogue: a SQLite-backed tagged flat file system over this storage layer — one
16
+ // source of truth for a user's binaries (the video gallery is a video-filtered view over it).
17
+ export { catalogueUploadKey, classifyFileType, createCatalogueMediaSource, createCataloguePhotoSource, createCatalogueVideoSource, createChunkedUploadService, createFilesStack, createFileToPlayable, createFileUrls, createLedgeredDeletes, createSqliteFileCatalogue, createStorageOpsLedger, createStorageReconciler, FILE_CATALOGUE_MIGRATIONS, fileBinaryKey, mountFileRoutes, } from './files';
18
+ export { mountMediaRoutes } from './mountMediaRoutes';
19
+ export { createDiskSource, createMediaUrls, SourceRegistry, } from './sources';
20
+ // The saas profile's `ObjectStore` (08 §1b). Its local counterpart is
21
+ // `createFsObjectStore` in `cwip/storage` — same interface, different issuer.
22
+ export { createS3ObjectStore, } from './s3ObjectStore';
23
+ export { decodeRange, serveRange } from './streamRange';
24
+ export { createStreamTokenService } from './streamToken';
25
+ export { fsHlsFileKey, fsHlsMasterKey, fsHlsPrefix, fsMp4Key, fsPosterKey, fsSourceKey, fsSpriteKey, fsVttKey, privateFsKey, } from './types';
26
+ export { processVideo, } from './videoProcessor';
27
+ export { objectStoreAsMediaStore } from './objectStoreMediaStore';
@@ -0,0 +1,28 @@
1
+ import type { Context, Hono } from 'hono';
2
+ import type { SourceRegistry } from './sources';
3
+ import type { StreamGrant, StreamTokenService } from './streamToken';
4
+ import { type MediaCtx, type MediaStore } from './types';
5
+ /** Resolves the Access-Control-Allow-Origin for a request origin: a fixed origin, an allowlist
6
+ * (echoes the request origin when allowed), or a custom resolver (return null to deny). */
7
+ export type CorsResolver = string | string[] | ((requestOrigin: string | undefined) => string | null | undefined);
8
+ export type MountMediaOptions = {
9
+ /** Byte backend shared by this mount (keys carry the owner). */
10
+ store: MediaStore;
11
+ /** Verifies the path token → owner/source/video grant. */
12
+ token: StreamTokenService;
13
+ /** On-server route mount path (path-only; must share its trailing path with the createMediaUrls
14
+ * baseUrl). Default '/_media'. */
15
+ basePath?: string;
16
+ /** Sources, for upload ingest. */
17
+ registry?: SourceRegistry;
18
+ /** Cross-origin access for split-origin deploys (media on a different origin than the SPA).
19
+ * Omit for same-origin. */
20
+ cors?: CorsResolver;
21
+ /** Authorizes an upload request (it CAN send the Bearer, unlike a media element). Return a
22
+ * MediaCtx to allow, or null to reject. Omit ⇒ uploads are disabled on this mount. */
23
+ authorizeUpload?: (c: Context) => Promise<MediaCtx | null> | MediaCtx | null;
24
+ /** Optional hook to decrypt bytes for the private (pfs/…) keyspace before serving. Wired by
25
+ * the private-media layer; omitted ⇒ all bytes served as stored. */
26
+ decryptStream?: (stream: ReadableStream<Uint8Array>, grant: StreamGrant) => ReadableStream<Uint8Array>;
27
+ };
28
+ export declare function mountMediaRoutes(app: Hono, opts: MountMediaOptions): void;
@@ -0,0 +1,141 @@
1
+ import { serveRange } from './streamRange';
2
+ import { fsDisplayKey, fsHlsFileKey, fsMp4Key, fsPosterKey, fsSourceKey, fsSpriteKey, fsVttKey, } from './types';
3
+ const EXPOSE_HEADERS = 'Content-Range, Content-Length, Accept-Ranges';
4
+ function resolveAcao(cors, reqOrigin) {
5
+ if (!cors)
6
+ return null;
7
+ if (typeof cors === 'string')
8
+ return cors;
9
+ if (Array.isArray(cors))
10
+ return reqOrigin && cors.includes(reqOrigin) ? reqOrigin : null;
11
+ return cors(reqOrigin) ?? null;
12
+ }
13
+ export function mountMediaRoutes(app, opts) {
14
+ const base = opts.basePath ?? '/_media';
15
+ const { store, token } = opts;
16
+ // Stamp CORS headers on a media response (and expose the range headers a cross-origin seeking
17
+ // <video> needs). A no-op when `cors` is unset (same-origin).
18
+ const cors = (c, res) => {
19
+ const acao = resolveAcao(opts.cors, c.req.header('origin'));
20
+ if (acao) {
21
+ res.headers.set('Access-Control-Allow-Origin', acao);
22
+ res.headers.set('Access-Control-Expose-Headers', EXPOSE_HEADERS);
23
+ res.headers.append('Vary', 'Origin');
24
+ }
25
+ return res;
26
+ };
27
+ // Preflight for cross-origin media/upload requests.
28
+ if (opts.cors) {
29
+ app.options(`${base}/*`, (c) => {
30
+ const acao = resolveAcao(opts.cors, c.req.header('origin'));
31
+ const headers = {
32
+ 'Access-Control-Allow-Methods': 'GET, PUT, OPTIONS',
33
+ 'Access-Control-Allow-Headers': 'Range, Content-Type, X-Filename, Authorization',
34
+ 'Access-Control-Max-Age': '86400',
35
+ };
36
+ if (acao) {
37
+ headers['Access-Control-Allow-Origin'] = acao;
38
+ headers.Vary = 'Origin';
39
+ }
40
+ return c.body(null, 204, headers);
41
+ });
42
+ }
43
+ // Resolve + validate the token a serve route carries, and confirm it matches the source/video
44
+ // in the path (so a token minted for video A can't fetch video B).
45
+ const grantFor = async (c, expectSource, expectVideo) => {
46
+ const raw = c.req.param('token');
47
+ if (!raw)
48
+ return null;
49
+ const grant = await token.verify(raw);
50
+ if (!grant)
51
+ return null;
52
+ if (grant.sourceId !== expectSource || grant.videoId !== expectVideo)
53
+ return null;
54
+ return grant;
55
+ };
56
+ // GET progressive MP4 (Range). Prefers the faststart derivative; falls back to the raw source
57
+ // when the pipeline hasn't produced one (ffmpeg absent / still processing).
58
+ app.get(`${base}/s/:token/media/:source/:video`, async (c) => {
59
+ const grant = await grantFor(c, c.req.param('source'), c.req.param('video'));
60
+ if (!grant)
61
+ return c.body(null, 401);
62
+ const mp4 = fsMp4Key(grant.owner, grant.videoId);
63
+ const key = (await store.stat(mp4)) ? mp4 : fsSourceKey(grant.owner, grant.videoId);
64
+ if (store.redirectUrl) {
65
+ const url = await store.redirectUrl(key, { mime: 'video/mp4' });
66
+ if (url)
67
+ return cors(c, c.redirect(url, 303));
68
+ }
69
+ return cors(c, await serveRange(store, key, 'video/mp4', c.req.header('range'), {
70
+ contentDisposition: 'inline',
71
+ }));
72
+ });
73
+ // GET an HLS file (master.m3u8 / <height>/playlist.m3u8 / <height>/seg_*.ts). Relative URIs
74
+ // inside the playlists resolve under this same /s/:token/hls/:source/:video/ prefix.
75
+ app.get(`${base}/s/:token/hls/:source/:video/*`, async (c) => {
76
+ const grant = await grantFor(c, c.req.param('source'), c.req.param('video'));
77
+ if (!grant)
78
+ return c.body(null, 401);
79
+ const prefix = `${base}/s/${c.req.param('token')}/hls/${grant.sourceId}/${grant.videoId}/`;
80
+ const rel = c.req.path.slice(prefix.length);
81
+ if (!rel || rel.includes('..'))
82
+ return c.body(null, 400);
83
+ const mime = rel.endsWith('.m3u8') ? 'application/vnd.apple.mpegurl' : 'video/mp2t';
84
+ return cors(c, await serveRange(store, fsHlsFileKey(grant.owner, grant.videoId, rel), mime, c.req.header('range')));
85
+ });
86
+ app.get(`${base}/s/:token/poster/:source/:video`, async (c) => {
87
+ const grant = await grantFor(c, c.req.param('source'), c.req.param('video'));
88
+ if (!grant)
89
+ return c.body(null, 401);
90
+ return cors(c, await serveRange(store, fsPosterKey(grant.owner, grant.videoId), 'image/jpeg', c.req.header('range')));
91
+ });
92
+ // Full-size browser-safe JPEG for images the browser can't render natively (HEIC/HEIF). Serves
93
+ // the display derivative; falls back to the small poster if it isn't built yet (the boot backfill
94
+ // produces it, so the fallback is momentary) — never a raw HEIC the browser would fail on.
95
+ app.get(`${base}/s/:token/display/:source/:video`, async (c) => {
96
+ const grant = await grantFor(c, c.req.param('source'), c.req.param('video'));
97
+ if (!grant)
98
+ return c.body(null, 401);
99
+ const displayKey = fsDisplayKey(grant.owner, grant.videoId);
100
+ const key = (await store.stat(displayKey))
101
+ ? displayKey
102
+ : fsPosterKey(grant.owner, grant.videoId);
103
+ return cors(c, await serveRange(store, key, 'image/jpeg', c.req.header('range')));
104
+ });
105
+ // Sprite sheet + its WebVTT track, served as siblings so the VTT's relative 'sprites.jpg' cue
106
+ // targets resolve to the jpg route.
107
+ app.get(`${base}/s/:token/sprite/:source/:video/:file`, async (c) => {
108
+ const grant = await grantFor(c, c.req.param('source'), c.req.param('video'));
109
+ if (!grant)
110
+ return c.body(null, 401);
111
+ const file = c.req.param('file');
112
+ if (file === 'sprites.vtt') {
113
+ return cors(c, await serveRange(store, fsVttKey(grant.owner, grant.videoId), 'text/vtt', c.req.header('range')));
114
+ }
115
+ if (file === 'sprites.jpg') {
116
+ return cors(c, await serveRange(store, fsSpriteKey(grant.owner, grant.videoId), 'image/jpeg', c.req.header('range')));
117
+ }
118
+ return c.body(null, 404);
119
+ });
120
+ // PUT upload → ingest. Guarded by the app-supplied authorizer (a fetch CAN send the Bearer).
121
+ const { authorizeUpload, registry } = opts;
122
+ if (authorizeUpload && registry) {
123
+ app.put(`${base}/upload/:source`, async (c) => {
124
+ const ctx = await authorizeUpload(c);
125
+ if (!ctx)
126
+ return c.json({ error: 'unauthorized' }, 401);
127
+ const source = registry.get(c.req.param('source'));
128
+ if (!source?.ingest)
129
+ return c.json({ error: 'source not found' }, 404);
130
+ const body = c.req.raw.body;
131
+ if (!body)
132
+ return c.json({ error: 'empty body' }, 400);
133
+ const video = await source.ingest(ctx, {
134
+ name: c.req.header('x-filename') ?? 'upload',
135
+ mime: c.req.header('content-type') ?? 'application/octet-stream',
136
+ body,
137
+ });
138
+ return c.json({ video });
139
+ });
140
+ }
141
+ }
@@ -0,0 +1,4 @@
1
+ import type { ObjectStore } from 'cwip/storage';
2
+ import type { MediaStore } from './types';
3
+ /** Adapt an {@link ObjectStore} to the {@link MediaStore} contract `processVideo` expects. */
4
+ export declare function objectStoreAsMediaStore(objects: ObjectStore): MediaStore;
@@ -0,0 +1,57 @@
1
+ // Bridge the framework's stream-based `ObjectStore` (cwip/storage) to the
2
+ // byte/stream `MediaStore` the proven `processVideo` pipeline drives (§2d).
3
+ //
4
+ // `processVideo` predates the engine `ObjectStore`: it speaks `MediaStore`
5
+ // (put(bytes)/putStream/get(bytes)/stat/localPath). The engine wires an
6
+ // `ObjectStore` (put(stream)/get(stream)/stat/delete/signedUrl). Rather than
7
+ // fork the whole transcode orchestrator onto a second store type, the asset
8
+ // `transcode` handler wraps its `ObjectStore` in this adapter and reuses the
9
+ // pipeline unchanged. There is deliberately NO `localPath`, so `processVideo`
10
+ // materialises the source by download — correct for a stream store where the
11
+ // bytes may live off-box (S3/R2).
12
+ const streamToBytes = async (stream) => new Uint8Array(await new Response(stream).arrayBuffer());
13
+ const bytesToStream = (bytes) => new ReadableStream({
14
+ start(controller) {
15
+ controller.enqueue(bytes);
16
+ controller.close();
17
+ },
18
+ });
19
+ /** Adapt an {@link ObjectStore} to the {@link MediaStore} contract `processVideo` expects. */
20
+ export function objectStoreAsMediaStore(objects) {
21
+ return {
22
+ async put(key, bytes, mime) {
23
+ await objects.put(key, bytesToStream(bytes), { contentType: mime });
24
+ },
25
+ async putStream(key, body, mime) {
26
+ await objects.put(key, body, { contentType: mime });
27
+ },
28
+ async get(key) {
29
+ const stream = await objects.get(key);
30
+ return stream ? await streamToBytes(stream) : null;
31
+ },
32
+ async remove(key) {
33
+ await objects.delete(key);
34
+ },
35
+ async removePrefix() {
36
+ // The `ObjectStore` contract has no prefix delete, and `processVideo` never
37
+ // calls this — surface misuse loudly rather than silently no-op.
38
+ throw new Error('objectStoreAsMediaStore.removePrefix is not supported over an ObjectStore.');
39
+ },
40
+ async stat(key) {
41
+ const stat = await objects.stat(key);
42
+ return stat ? { size: stat.size } : null;
43
+ },
44
+ async getRange(key, range) {
45
+ const stream = await objects.get(key);
46
+ if (!stream)
47
+ return null;
48
+ if (!range)
49
+ return { stream };
50
+ // Whole-object read then slice: unused by `processVideo`, present only to
51
+ // satisfy the interface. A hot Range-serving path uses the real store.
52
+ const bytes = await streamToBytes(stream);
53
+ return { stream: bytesToStream(bytes.subarray(range.start, range.end + 1)) };
54
+ },
55
+ // No `localPath`: force `processVideo` down its download path (stream store).
56
+ };
57
+ }