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,104 @@
1
+ // The binary-server transcode API as a plain job client — submit, poll, and the two
2
+ // derivative shapes the fleet actually asks for.
3
+ //
4
+ // It was already here, inlined inside `createBinaryServerProcessing`, but only reachable
5
+ // through that module's two seams (`VideoProcessor` / `ImageDerivatives`), which are shaped
6
+ // for the cursedbelt file CATALOGUE: they take a `MediaStore`, a `workDir`, an owner and a
7
+ // catalogue id. An app that stores binaries some other way — apps/notes keys attachments by
8
+ // sha256 through satellite-kit's binary-store and has no catalogue at all — could not reach
9
+ // the pipeline without either adopting the catalogue or writing a second HTTP client.
10
+ //
11
+ // So the transport lives here and the catalogue seams are built on top of it. There is ONE
12
+ // derivative pipeline in the fleet, and this is its entry point.
13
+ //
14
+ // ── Why binary-server rather than deriving locally ──────────────────────────────────────
15
+ // Derivatives are made where the SOURCE BYTES already are. binary-server is the fleet's
16
+ // binary store, it runs on a Mac with real CPU, disk, ffmpeg and (for HEIC) sips, and the
17
+ // derived bytes land back in the same tenant beside their source. Pulling a multi-GB video
18
+ // down to an app host to run ffmpeg there defeats the point, and the shared prod t3.micro
19
+ // has neither the disk nor the ffmpeg to do it at all.
20
+ //
21
+ // ── Who may call it ─────────────────────────────────────────────────────────────────────
22
+ // `/api/transcode` is server-to-server: it is gated on the shared internal secret AND, since
23
+ // the 2026-08-05 delete-surface hardening, 404s outright from binary-server's public host.
24
+ // A prod-hosted app therefore CANNOT drive it; the Mac side of an app must.
25
+ const DEFAULT_POLL_MS = 1500;
26
+ const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
27
+ export function createTranscodeJobClient(cfg) {
28
+ const fetchImpl = cfg.fetchImpl ?? fetch;
29
+ const sleep = cfg.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
30
+ const pollMs = cfg.pollMs ?? DEFAULT_POLL_MS;
31
+ const timeoutMs = cfg.timeoutMs ?? DEFAULT_TIMEOUT_MS;
32
+ const appKey = (key) => `${cfg.appId}/${key}`;
33
+ const headers = {
34
+ 'content-type': 'application/json',
35
+ 'x-internal-secret': cfg.internalSecret,
36
+ };
37
+ const submit = async (body) => {
38
+ const res = await fetchImpl(`${cfg.baseUrl}/api/transcode`, {
39
+ method: 'POST',
40
+ headers,
41
+ body: JSON.stringify(body),
42
+ });
43
+ if (res.status !== 202) {
44
+ throw new Error(`binary-server transcode submit: ${res.status} ${await res.text()}`);
45
+ }
46
+ const { jobId } = (await res.json());
47
+ return jobId;
48
+ };
49
+ const poll = async (jobId, onProgress) => {
50
+ const deadline = Date.now() + timeoutMs;
51
+ for (;;) {
52
+ const res = await fetchImpl(`${cfg.baseUrl}/api/transcode/${encodeURIComponent(jobId)}`, {
53
+ headers,
54
+ });
55
+ if (!res.ok)
56
+ throw new Error(`binary-server transcode poll: ${res.status}`);
57
+ const job = (await res.json());
58
+ if (job.status === 'ready' || job.status === 'failed')
59
+ return job;
60
+ if (typeof job.progress === 'number')
61
+ onProgress?.(job.progress);
62
+ if (Date.now() > deadline)
63
+ throw new Error(`binary-server transcode ${jobId}: timed out`);
64
+ await sleep(pollMs);
65
+ }
66
+ };
67
+ const imageThumbnail = async (opts) => {
68
+ const jobId = await submit({
69
+ strategy: 'image',
70
+ nodeId: opts.nodeId ?? opts.sourceKey,
71
+ sourceKey: appKey(opts.sourceKey),
72
+ // The image strategy reads `binaryKey` as the source it fetches; the two are the same
73
+ // object here because nothing is being replaced, only derived from.
74
+ binaryKey: appKey(opts.sourceKey),
75
+ posterKey: appKey(opts.thumbKey),
76
+ ...(opts.displayKey ? { displayKey: appKey(opts.displayKey) } : {}),
77
+ });
78
+ const job = await poll(jobId);
79
+ if (job.status !== 'ready')
80
+ return { hasThumbnail: false, hasDisplay: false };
81
+ return {
82
+ hasThumbnail: job.result?.hasThumbnail ?? false,
83
+ hasDisplay: job.result?.hasDisplay ?? false,
84
+ };
85
+ };
86
+ const videoPoster = async (opts) => {
87
+ const jobId = await submit({
88
+ strategy: 'library',
89
+ nodeId: opts.nodeId ?? opts.sourceKey,
90
+ sourceKey: appKey(opts.sourceKey),
91
+ binaryKey: appKey(opts.sourceKey),
92
+ posterKey: appKey(opts.posterKey),
93
+ posterOnly: true,
94
+ });
95
+ const job = await poll(jobId);
96
+ if (job.status !== 'ready')
97
+ return { hasPoster: false, durationSeconds: 0 };
98
+ return {
99
+ hasPoster: job.result?.hasPoster ?? false,
100
+ durationSeconds: job.result?.durationSeconds ?? 0,
101
+ };
102
+ };
103
+ return { appKey, submit, poll, imageThumbnail, videoPoster };
104
+ }
@@ -0,0 +1,193 @@
1
+ import type { ProcessingStatus } from 'cursedbelt-core/media';
2
+ /** Identity/tenant context threaded from an app's auth into every store/source call, so keys
3
+ * namespace per-owner and one tenant can never read another's media. */
4
+ export type MediaCtx = {
5
+ userId: string;
6
+ tenantId: string;
7
+ };
8
+ /**
9
+ * The byte-storage adapter. The caller owns the key namespace (see keyspace helpers). One
10
+ * interface, many backends. Web-standard streams throughout (Bun/Hono) — the stream hands
11
+ * straight to a `Response` body with no node-stream conversion.
12
+ */
13
+ export interface MediaStore {
14
+ /** Store bytes under an opaque key. */
15
+ put(key: string, bytes: Uint8Array, mime: string): Promise<void>;
16
+ /** Stream a body to storage without buffering it — for multi-GB writes (a transcode source,
17
+ * HLS segments) where a Uint8Array would be a full heap copy. */
18
+ putStream(key: string, body: ReadableStream<Uint8Array>, mime: string, opts?: {
19
+ contentLength?: number;
20
+ }): Promise<void>;
21
+ /** Read bytes back, or null if the key is gone. */
22
+ get(key: string): Promise<Uint8Array | null>;
23
+ /** Best-effort delete; must not throw if the key is already absent. */
24
+ remove(key: string): Promise<void>;
25
+ /** Recursively delete everything under a key prefix (an HLS tree, a user's media). */
26
+ removePrefix(prefix: string): Promise<void>;
27
+ /** Total byte size of a stored object, or null if gone. Answers Range requests without
28
+ * reading the whole object first. */
29
+ stat(key: string): Promise<{
30
+ size: number;
31
+ } | null>;
32
+ /** A web stream for the inclusive byte range [start, end], or the whole object when `range`
33
+ * is omitted; null if gone. Serves 206 Partial Content for seeking. */
34
+ getRange(key: string, range?: {
35
+ start: number;
36
+ end: number;
37
+ }): Promise<{
38
+ stream: ReadableStream<Uint8Array>;
39
+ } | null>;
40
+ /** Absolute on-disk path for a key if this backend is local — lets ffmpeg read the source
41
+ * directly. null/absent for remote backends (the pipeline downloads the source first). */
42
+ localPath?(key: string): Promise<string | null>;
43
+ /** For a user-facing download, some backends (binary-server) prefer redirecting the browser
44
+ * to a signed edge URL instead of proxying bytes. Returns the URL, or null to fall back. */
45
+ redirectUrl?(key: string, opts: {
46
+ mime: string;
47
+ filename?: string;
48
+ }): Promise<string | null>;
49
+ }
50
+ /**
51
+ * Claims binary-server verifies (see binary-server/src/tokens.ts). This is the ONE
52
+ * canonical shape for a binary-server file token across the fleet: cursedbelt's
53
+ * injected-signer `createBinaryServerStore` and `@satellites/satellite-kit`'s
54
+ * self-signing `createBinaryStore` both mint tokens against the SAME binary-server, so
55
+ * the claim set must never drift — satellite-kit imports this exact type rather than
56
+ * redeclaring it, so a token one side signs is always shaped for the other to read. The
57
+ * app's signer turns these into an RS256 JWT.
58
+ */
59
+ export type FileTokenClaims = {
60
+ /** Exact authorized path, app-prefixed: `<app>/<key>`. */
61
+ k?: string;
62
+ /** Authorized path prefix, app-prefixed (container trees, e.g. an HLS tree). */
63
+ p?: string;
64
+ /** Acting user/app id — audit trail on binary-server's events, never authz. */
65
+ uid?: string;
66
+ /** Chunk-session id (must match across every chunk of one upload). */
67
+ sid?: string;
68
+ /** Chunk index (0-based). */
69
+ ci?: number;
70
+ /** Total chunk count. */
71
+ tc?: number;
72
+ /** Download filename → Content-Disposition. */
73
+ fn?: string;
74
+ dl?: 'inline' | 'attachment';
75
+ /** Per-op scope. A DELETE against binary-server requires a token carrying `op: 'delete'`
76
+ * (in addition to the internal secret) — a plain read/download token can never be replayed
77
+ * as a delete, and the token's issuer + `k`/`p` tenant-scope the delete. Owner ruling #10. */
78
+ op?: 'delete';
79
+ };
80
+ export type FileTokenSigner = (claims: FileTokenClaims) => Promise<string>;
81
+ export type BinaryServerConfig = {
82
+ /** e.g. 'https://binary-server.cursedalchemy.com'. */
83
+ baseUrl: string;
84
+ /** App namespace (path + token scope), e.g. 'cursedalchemy'. */
85
+ appId: string;
86
+ /** App-provided RS256 signer (app holds the private key). */
87
+ signFileToken: FileTokenSigner;
88
+ /** Shared secret for internal ops (delete). Sent as x-internal-secret. */
89
+ internalSecret?: string;
90
+ };
91
+ /** A source of videos for a gallery/catalogue. Apps implement this differently (a disk folder,
92
+ * a binary-server app, ca's Files tree + private set). Consumers are source-agnostic. */
93
+ export interface MediaSource {
94
+ id: string;
95
+ title: string;
96
+ list(ctx: MediaCtx, opts: {
97
+ cursor?: string;
98
+ tag?: string;
99
+ query?: string;
100
+ limit?: number;
101
+ /** Listing order (backend maps to its own sorts; default newest-first). `random` is a
102
+ * seeded shuffle (stable across its own pagination); `lastViewed` orders by watch-open
103
+ * recency. A cursor is only valid under the sort that minted it. */
104
+ sort?: 'recent' | 'oldest' | 'name' | 'size' | 'random' | 'lastViewed';
105
+ }): Promise<{
106
+ items: import('cursedbelt-core/media').PlayableVideo[];
107
+ nextCursor?: string;
108
+ }>;
109
+ resolve(ctx: MediaCtx, videoId: string): Promise<import('cursedbelt-core/media').PlayableVideo | null>;
110
+ tags?(ctx: MediaCtx): Promise<string[]>;
111
+ ingest?(ctx: MediaCtx, upload: {
112
+ name: string;
113
+ mime: string;
114
+ body: ReadableStream<Uint8Array>;
115
+ size?: number;
116
+ }): Promise<import('cursedbelt-core/media').PlayableVideo>;
117
+ remove?(ctx: MediaCtx, videoId: string): Promise<void>;
118
+ /** Record a watch-open of a video — the signal the `lastViewed` sort orders by. Optional: a
119
+ * read-only source (a disk folder) has nothing to record. */
120
+ recordView?(ctx: MediaCtx, videoId: string): Promise<void>;
121
+ }
122
+ /** A source of photos for an image gallery — the image sibling of {@link MediaSource}. Same
123
+ * shape (cursor listing, tag facets, ingest, view ledger), photo-typed items. */
124
+ export interface PhotoSource {
125
+ id: string;
126
+ title: string;
127
+ list(ctx: MediaCtx, opts: {
128
+ cursor?: string;
129
+ tag?: string;
130
+ query?: string;
131
+ limit?: number;
132
+ sort?: 'recent' | 'oldest' | 'name' | 'size' | 'random' | 'lastViewed';
133
+ }): Promise<{
134
+ items: import('cursedbelt-core/media').GalleryPhoto[];
135
+ nextCursor?: string;
136
+ }>;
137
+ resolve(ctx: MediaCtx, photoId: string): Promise<import('cursedbelt-core/media').GalleryPhoto | null>;
138
+ tags?(ctx: MediaCtx): Promise<string[]>;
139
+ ingest?(ctx: MediaCtx, upload: {
140
+ name: string;
141
+ mime: string;
142
+ body: ReadableStream<Uint8Array>;
143
+ size?: number;
144
+ tags?: string[];
145
+ }): Promise<import('cursedbelt-core/media').GalleryPhoto>;
146
+ remove?(ctx: MediaCtx, photoId: string): Promise<void>;
147
+ recordView?(ctx: MediaCtx, photoId: string): Promise<void>;
148
+ }
149
+ /** A source of MIXED media (photos + videos) for a combined gallery — the union of
150
+ * {@link MediaSource} and {@link PhotoSource} over ONE catalogue. `list`/`resolve`
151
+ * yield `GalleryMedia` (a `mediaKind`-tagged photo|video), and a `kind` filter narrows
152
+ * to just photos or just videos. Same cursor/tag/sort listing contract as its siblings. */
153
+ export interface MediaGallerySource {
154
+ id: string;
155
+ title: string;
156
+ list(ctx: MediaCtx, opts: {
157
+ /** Narrow the mixed feed — `all` (default) lists photos + videos. */
158
+ kind?: import('cursedbelt-core/media').MediaKindFilter;
159
+ cursor?: string;
160
+ tag?: string;
161
+ query?: string;
162
+ limit?: number;
163
+ sort?: 'recent' | 'oldest' | 'name' | 'size' | 'random' | 'lastViewed';
164
+ }): Promise<{
165
+ items: import('cursedbelt-core/media').GalleryMedia[];
166
+ nextCursor?: string;
167
+ }>;
168
+ resolve(ctx: MediaCtx, itemId: string): Promise<import('cursedbelt-core/media').GalleryMedia | null>;
169
+ tags?(ctx: MediaCtx): Promise<string[]>;
170
+ remove?(ctx: MediaCtx, itemId: string): Promise<void>;
171
+ recordView?(ctx: MediaCtx, itemId: string): Promise<void>;
172
+ }
173
+ export type { ProcessingStatus };
174
+ export declare const fsSourceKey: (owner: string, id: string) => string;
175
+ export declare const fsMp4Key: (owner: string, id: string) => string;
176
+ export declare const fsHlsPrefix: (owner: string, id: string) => string;
177
+ export declare const fsHlsMasterKey: (owner: string, id: string) => string;
178
+ export declare const fsHlsFileKey: (owner: string, id: string, rel: string) => string;
179
+ export declare const fsPosterKey: (owner: string, id: string) => string;
180
+ /** Full-size browser-safe JPEG derivative for images the browser can't render natively (HEIC/HEIF).
181
+ * The lightbox's full-view URL points here instead of the raw original. */
182
+ export declare const fsDisplayKey: (owner: string, id: string) => string;
183
+ export declare const fsSpriteKey: (owner: string, id: string) => string;
184
+ export declare const fsVttKey: (owner: string, id: string) => string;
185
+ /** Private (encrypted) namespace — the private-media layer stores ciphertext here. The HLS
186
+ * segments under it are AES-128-encrypted at rest (the player fetches the content key via the
187
+ * gated key route and decrypts); a distinct keyspace from public `hls/` so the two never collide. */
188
+ export declare const privateFsKey: (owner: string, id: string) => string;
189
+ export declare const privateHlsPrefix: (owner: string, id: string) => string;
190
+ export declare const privateHlsMasterKey: (owner: string, id: string) => string;
191
+ export declare const privateHlsFileKey: (owner: string, id: string, rel: string) => string;
192
+ export declare const privatePosterKey: (owner: string, id: string) => string;
193
+ export declare const privateSourceKey: (owner: string, id: string) => string;
@@ -0,0 +1,41 @@
1
+ // cursedbelt-server/storage — the byte-storage layer cursedbelt's media pipeline was
2
+ // missing. `server/media` (media-bun) owns the codec/pixel work; this owns *where the bytes
3
+ // live and how they're served*: a backend-agnostic MediaStore (disk / binary-server / S3), a
4
+ // Range→Response helper for seekable <video>, path-embedded stream tokens (so a media element
5
+ // with no Bearer can still be authorized), a source registry, and the ingest→derivatives
6
+ // pipeline glue. Ported/generalised from ca's proven media stack; the app maps its own records
7
+ // to a MediaSource. Pairs with the `PlayableVideo` contract in `cursedbelt-core/media`.
8
+ //
9
+ // 🔴 THIS FILE IS A PUBLISHED LEAF — `cursedbelt-server/storage/types` — AND IT MUST STAY
10
+ // IMPORT-FREE AT RUNTIME. `src/leafSubpathsImportNothing.spec.ts` imports it in a fixture with
11
+ // no packages resolvable at all and fails if it throws, so an `import` here that survives
12
+ // transpilation (anything that is not `import type`) turns that spec red rather than turning
13
+ // up in a consumer's install. The subpath exists because `./server/storage`'s own index is a
14
+ // barrel: it reaches `streamToken` → `auth/jwt` → jose, the whole SQLite file catalogue under
15
+ // `files/`, and a lazily-imported `sharp` — while an app that wants only the SHAPE of a file
16
+ // token (apps/collections wanted exactly `FileTokenClaims`, a type TypeScript erases) was made
17
+ // to install a one-time-password library to get it. Add types here freely; add a value import
18
+ // here never.
19
+ // ── Keyspace helpers ──────────────────────────────────────────────────────────────
20
+ // One source of truth for every storage key. Sources + serve routes + the pipeline all
21
+ // address bytes through these, so a key layout is never hand-spelled twice.
22
+ export const fsSourceKey = (owner, id) => `source/${owner}/${id}`;
23
+ export const fsMp4Key = (owner, id) => `fs/${owner}/${id}.mp4`;
24
+ export const fsHlsPrefix = (owner, id) => `hls/${owner}/${id}`;
25
+ export const fsHlsMasterKey = (owner, id) => `hls/${owner}/${id}/master.m3u8`;
26
+ export const fsHlsFileKey = (owner, id, rel) => `hls/${owner}/${id}/${rel}`;
27
+ export const fsPosterKey = (owner, id) => `fs/${owner}/${id}-poster.jpg`;
28
+ /** Full-size browser-safe JPEG derivative for images the browser can't render natively (HEIC/HEIF).
29
+ * The lightbox's full-view URL points here instead of the raw original. */
30
+ export const fsDisplayKey = (owner, id) => `fs/${owner}/${id}-display.jpg`;
31
+ export const fsSpriteKey = (owner, id) => `fs/${owner}/${id}-sprites.jpg`;
32
+ export const fsVttKey = (owner, id) => `fs/${owner}/${id}-sprites.vtt`;
33
+ /** Private (encrypted) namespace — the private-media layer stores ciphertext here. The HLS
34
+ * segments under it are AES-128-encrypted at rest (the player fetches the content key via the
35
+ * gated key route and decrypts); a distinct keyspace from public `hls/` so the two never collide. */
36
+ export const privateFsKey = (owner, id) => `pfs/${owner}/${id}`;
37
+ export const privateHlsPrefix = (owner, id) => `pfs/${owner}/${id}/hls`;
38
+ export const privateHlsMasterKey = (owner, id) => `pfs/${owner}/${id}/hls/master.m3u8`;
39
+ export const privateHlsFileKey = (owner, id, rel) => `pfs/${owner}/${id}/hls/${rel}`;
40
+ export const privatePosterKey = (owner, id) => `pfs/${owner}/${id}/poster.enc`;
41
+ export const privateSourceKey = (owner, id) => `pfs/${owner}/${id}/source.enc`;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * ── The NAMES of every video derivative binary-server writes ────────────────────────────
3
+ *
4
+ * A derivative's key is COMPUTED from its source's, never bookkept. A consumer finds one by
5
+ * deriving the name and asking `/meta` whether it exists yet, so there is no webhook, no job
6
+ * id, no schema column and nothing to reconcile: the absence of the object IS the "not ready
7
+ * yet" state, and it answers itself.
8
+ *
9
+ * ── 🔴 Why these live in cursedbelt ─────────────────────────────────────────────────────
10
+ * Owner, 2026-08-14: *"Make it in cb because many apps may move to cloudflare and share the
11
+ * logic."* This contract is the seam between three repos that must never disagree —
12
+ * binary-server WRITES these names, `@satellites/satellite-kit` re-exports them for the
13
+ * satellite apps, and cursedbelt's own `binaryServerStore` mints the tokens that authorize
14
+ * them. A second hand-typed `${key}.web.mp4` anywhere in that triangle is a silent
15
+ * no-preview the day binary-server renames anything, and that has already happened once:
16
+ * `apps/collections` had the contract and `apps/family` shipped video without it, so family
17
+ * had no faststart stream, no scrub previews and no processing state at all.
18
+ *
19
+ * 🔴 What is shared here is the STRATEGY, never the media. Nothing in this module can resolve
20
+ * a stored object — every function is a pure string transform over a key the caller already
21
+ * holds. Each app is its own binary-server tenant with its own signing key, so family cannot
22
+ * reach a collections object through anything here, and that must stay true.
23
+ *
24
+ * ── 🔴 This is NOT the `fs*` keyspace in `./types.ts`, and they must not be merged ───────
25
+ * `fsHlsMasterKey(owner, id)` → `hls/<owner>/<id>/master.m3u8` and its siblings address the
26
+ * FileCatalogue: a caller that holds an owner and an item id, whose derivatives live in their
27
+ * own top-level namespaces. This module addresses a caller that holds a binary-server KEY and
28
+ * derives sibling names from it, which is what makes "ask /meta for a name you computed" work
29
+ * with no catalogue at all.
30
+ *
31
+ * Two consumers, two shapes, both correct:
32
+ * · `fs*` — cursedbelt's own FileCatalogue / the `library` transcode strategy.
33
+ * · this module — binary-server's derivative convention, used by the satellite apps.
34
+ *
35
+ * Collapsing either into the other renames objects that already exist in the store, and a
36
+ * renamed derivative is indistinguishable from an unbuilt one (absence is the "not ready"
37
+ * state) — so the visible result is every video in the fleet losing its previews at once,
38
+ * silently, with the rebuild costing hours of ffmpeg.
39
+ */
40
+ /**
41
+ * The browser-playable remux: the same pictures with the index at the front.
42
+ *
43
+ * A phone MP4/MOV stores its `moov` index at the END, and a browser cannot decode frame one
44
+ * until it has that index — so it downloads the whole file first. Measured on the owner's
45
+ * 90,339,606-byte upload (2026-08-13): the index began at byte 90,305,514, 99.96% of the way
46
+ * in. That is the "videos stay on a spinner for minutes" report, and no amount of Range
47
+ * support, caching or edge tuning fixes it. A `+faststart` remux is a STREAM COPY for
48
+ * h264+aac sources, which is what phones produce: no re-encode, nothing lost.
49
+ */
50
+ /**
51
+ * ── 🔴 THE ENCODER GENERATION IN THE NAME (2026-09-09) ──────────────────────────────────
52
+ *
53
+ * binary-server rebuilds every video derivative when its encoder improves, and a rebuild
54
+ * used to write **the same filename with different bytes**. That silently cancels out an
55
+ * `immutable` cache lifetime — a browser told `immutable` never re-checks, for a year, and
56
+ * **that cannot be revoked for a copy already on somebody's phone**. So the owner's decision
57
+ * (A, 2026-09-09) is that new bytes get a new address: `clip.web.mp4` → `clip.g2.web.mp4`.
58
+ *
59
+ * 🔴 **Generation 1 is UNTAGGED and always will be.** Every derivative in the store today is
60
+ * at the untagged name; tagging generation 1 would orphan all of them and re-encode the
61
+ * whole library for byte-identical output. Untagged is not a legacy case — it IS generation
62
+ * one.
63
+ *
64
+ * 🔴 **This constant must match binary-server's `VIDEO_ENCODER_GENERATION`**
65
+ * (`src/media/encoderGeneration.ts`). They are in different repos and neither can import the
66
+ * other, so binary-server carries a test that goes RED the moment somebody bumps its number,
67
+ * naming this file. If you are here because of that test: bump this in the same commit, and
68
+ * remember that `notes`, `collections` and `family` all reach these names through
69
+ * `@satellites/satellite-kit/video-derivatives`. Getting it wrong is not an error anywhere —
70
+ * it is a 404 that reads as "no preview available", on every video.
71
+ */
72
+ export declare const VIDEO_KEY_GENERATION = 1;
73
+ /** The generation tag a derivative's name carries. Empty for generation 1 — see above. */
74
+ export declare const videoGenerationTag: (generation?: number) => string;
75
+ export declare const webPlayableKeyFor: (key: string, generation?: number) => string;
76
+ /** The scrub-preview sprite sheet — tiled frames from across the clip, as one JPEG. */
77
+ export declare const spriteKeyFor: (key: string) => string;
78
+ /** The WebVTT mapping a playhead position onto a tile of {@link spriteKeyFor}. */
79
+ export declare const spriteVttKeyFor: (key: string) => string;
80
+ /**
81
+ * The single still a gallery draws on a tile.
82
+ *
83
+ * Deliberately the SAME name a still photograph's thumbnail gets, so a consumer has one rule
84
+ * — *the tile for anything is `<key>.thumb.jpg`, and its absence means not ready* — rather
85
+ * than one rule per media kind. The sprite sheet is not a substitute: it is a GRID, so drawing
86
+ * it on a tile shows a contact sheet, and using one cell means every consumer carries the
87
+ * VTT's geometry in order to crop it.
88
+ */
89
+ export declare const posterKeyFor: (key: string) => string;
90
+ /**
91
+ * ── The HLS tree: `<key>.hls/` ──────────────────────────────────────────────────────────
92
+ *
93
+ * 🔴 ONE container, and the trailing name is load-bearing. binary-server's `storage.ts`
94
+ * co-locates a container's whole subtree under a single shard and `removeContainer()` wipes it
95
+ * atomically — so a ladder that is one container is deleted, migrated and reconciled as one
96
+ * thing. Segments scattered under sibling keys would be none of those, and orphaning half a
97
+ * ladder is worse than having none: the master would keep advertising rungs whose segments
98
+ * 404, which a player retries rather than falls back from.
99
+ *
100
+ * 🔴 Why HLS at all, now that Range works. The Range bug (an edge Worker answering 200 + the
101
+ * whole 209 MB to a ranged read) was fixed 2026-08-14 and is NOT what this is for. A single
102
+ * progressive MP4, however well served, cannot ADAPT: a weak cell connection stalls on a
103
+ * 23 Mbps master instead of dropping to a lower rung. Each segment is also a small object that
104
+ * caches WHOLE at the edge, which sidesteps both the 512 MiB single-entry ceiling and the
105
+ * segment-assembly machinery that exists to work around it for big films.
106
+ */
107
+ export declare const hlsPrefixFor: (key: string, generation?: number) => string;
108
+ /**
109
+ * The playlist a player is pointed at — the one object whose presence means "the ladder is
110
+ * ready". It is written LAST, after every rung, so a consumer that finds it can trust that
111
+ * what it names exists. Absence is normal and means "play the faststart remux".
112
+ */
113
+ export declare const hlsMasterKeyFor: (key: string, generation?: number) => string;
114
+ /**
115
+ * Every derivative suffix, for the "is this key itself a derivative" question.
116
+ *
117
+ * 🔴 Load-bearing in three directions, each of which has already cost something:
118
+ * 1. A derivative must never become the source of another, or a sweep builds
119
+ * `clip.web.mp4.web.mp4` and then `…web.mp4.web.mp4.web.mp4`, forever.
120
+ * 2. A sweep must not count a derivative as work still owed, or the backlog is permanently
121
+ * non-empty and ffmpeg re-runs on every pass.
122
+ * 3. A duplicate scan must not propose a file against its own derivative. They share a title
123
+ * by construction, so every video would pair with its own remux and a review queue would
124
+ * contain nothing else.
125
+ */
126
+ export declare const DERIVATIVE_SUFFIXES: readonly [".web.mp4", ".sprite.jpg", ".sprite.vtt", ".thumb.jpg", ".web.jpg"];
127
+ export declare const isDerivativeKey: (key: string) => boolean;
@@ -0,0 +1,149 @@
1
+ /**
2
+ * ── The NAMES of every video derivative binary-server writes ────────────────────────────
3
+ *
4
+ * A derivative's key is COMPUTED from its source's, never bookkept. A consumer finds one by
5
+ * deriving the name and asking `/meta` whether it exists yet, so there is no webhook, no job
6
+ * id, no schema column and nothing to reconcile: the absence of the object IS the "not ready
7
+ * yet" state, and it answers itself.
8
+ *
9
+ * ── 🔴 Why these live in cursedbelt ─────────────────────────────────────────────────────
10
+ * Owner, 2026-08-14: *"Make it in cb because many apps may move to cloudflare and share the
11
+ * logic."* This contract is the seam between three repos that must never disagree —
12
+ * binary-server WRITES these names, `@satellites/satellite-kit` re-exports them for the
13
+ * satellite apps, and cursedbelt's own `binaryServerStore` mints the tokens that authorize
14
+ * them. A second hand-typed `${key}.web.mp4` anywhere in that triangle is a silent
15
+ * no-preview the day binary-server renames anything, and that has already happened once:
16
+ * `apps/collections` had the contract and `apps/family` shipped video without it, so family
17
+ * had no faststart stream, no scrub previews and no processing state at all.
18
+ *
19
+ * 🔴 What is shared here is the STRATEGY, never the media. Nothing in this module can resolve
20
+ * a stored object — every function is a pure string transform over a key the caller already
21
+ * holds. Each app is its own binary-server tenant with its own signing key, so family cannot
22
+ * reach a collections object through anything here, and that must stay true.
23
+ *
24
+ * ── 🔴 This is NOT the `fs*` keyspace in `./types.ts`, and they must not be merged ───────
25
+ * `fsHlsMasterKey(owner, id)` → `hls/<owner>/<id>/master.m3u8` and its siblings address the
26
+ * FileCatalogue: a caller that holds an owner and an item id, whose derivatives live in their
27
+ * own top-level namespaces. This module addresses a caller that holds a binary-server KEY and
28
+ * derives sibling names from it, which is what makes "ask /meta for a name you computed" work
29
+ * with no catalogue at all.
30
+ *
31
+ * Two consumers, two shapes, both correct:
32
+ * · `fs*` — cursedbelt's own FileCatalogue / the `library` transcode strategy.
33
+ * · this module — binary-server's derivative convention, used by the satellite apps.
34
+ *
35
+ * Collapsing either into the other renames objects that already exist in the store, and a
36
+ * renamed derivative is indistinguishable from an unbuilt one (absence is the "not ready"
37
+ * state) — so the visible result is every video in the fleet losing its previews at once,
38
+ * silently, with the rebuild costing hours of ffmpeg.
39
+ */
40
+ /**
41
+ * The browser-playable remux: the same pictures with the index at the front.
42
+ *
43
+ * A phone MP4/MOV stores its `moov` index at the END, and a browser cannot decode frame one
44
+ * until it has that index — so it downloads the whole file first. Measured on the owner's
45
+ * 90,339,606-byte upload (2026-08-13): the index began at byte 90,305,514, 99.96% of the way
46
+ * in. That is the "videos stay on a spinner for minutes" report, and no amount of Range
47
+ * support, caching or edge tuning fixes it. A `+faststart` remux is a STREAM COPY for
48
+ * h264+aac sources, which is what phones produce: no re-encode, nothing lost.
49
+ */
50
+ /**
51
+ * ── 🔴 THE ENCODER GENERATION IN THE NAME (2026-09-09) ──────────────────────────────────
52
+ *
53
+ * binary-server rebuilds every video derivative when its encoder improves, and a rebuild
54
+ * used to write **the same filename with different bytes**. That silently cancels out an
55
+ * `immutable` cache lifetime — a browser told `immutable` never re-checks, for a year, and
56
+ * **that cannot be revoked for a copy already on somebody's phone**. So the owner's decision
57
+ * (A, 2026-09-09) is that new bytes get a new address: `clip.web.mp4` → `clip.g2.web.mp4`.
58
+ *
59
+ * 🔴 **Generation 1 is UNTAGGED and always will be.** Every derivative in the store today is
60
+ * at the untagged name; tagging generation 1 would orphan all of them and re-encode the
61
+ * whole library for byte-identical output. Untagged is not a legacy case — it IS generation
62
+ * one.
63
+ *
64
+ * 🔴 **This constant must match binary-server's `VIDEO_ENCODER_GENERATION`**
65
+ * (`src/media/encoderGeneration.ts`). They are in different repos and neither can import the
66
+ * other, so binary-server carries a test that goes RED the moment somebody bumps its number,
67
+ * naming this file. If you are here because of that test: bump this in the same commit, and
68
+ * remember that `notes`, `collections` and `family` all reach these names through
69
+ * `@satellites/satellite-kit/video-derivatives`. Getting it wrong is not an error anywhere —
70
+ * it is a 404 that reads as "no preview available", on every video.
71
+ */
72
+ export const VIDEO_KEY_GENERATION = 1;
73
+ /** The generation tag a derivative's name carries. Empty for generation 1 — see above. */
74
+ export const videoGenerationTag = (generation = VIDEO_KEY_GENERATION) => generation <= 1 ? "" : `.g${generation}`;
75
+ export const webPlayableKeyFor = (key, generation = VIDEO_KEY_GENERATION) => `${key}${videoGenerationTag(generation)}.web.mp4`;
76
+ /** The scrub-preview sprite sheet — tiled frames from across the clip, as one JPEG. */
77
+ export const spriteKeyFor = (key) => `${key}.sprite.jpg`;
78
+ /** The WebVTT mapping a playhead position onto a tile of {@link spriteKeyFor}. */
79
+ export const spriteVttKeyFor = (key) => `${key}.sprite.vtt`;
80
+ /**
81
+ * The single still a gallery draws on a tile.
82
+ *
83
+ * Deliberately the SAME name a still photograph's thumbnail gets, so a consumer has one rule
84
+ * — *the tile for anything is `<key>.thumb.jpg`, and its absence means not ready* — rather
85
+ * than one rule per media kind. The sprite sheet is not a substitute: it is a GRID, so drawing
86
+ * it on a tile shows a contact sheet, and using one cell means every consumer carries the
87
+ * VTT's geometry in order to crop it.
88
+ */
89
+ export const posterKeyFor = (key) => `${key}.thumb.jpg`;
90
+ /**
91
+ * ── The HLS tree: `<key>.hls/` ──────────────────────────────────────────────────────────
92
+ *
93
+ * 🔴 ONE container, and the trailing name is load-bearing. binary-server's `storage.ts`
94
+ * co-locates a container's whole subtree under a single shard and `removeContainer()` wipes it
95
+ * atomically — so a ladder that is one container is deleted, migrated and reconciled as one
96
+ * thing. Segments scattered under sibling keys would be none of those, and orphaning half a
97
+ * ladder is worse than having none: the master would keep advertising rungs whose segments
98
+ * 404, which a player retries rather than falls back from.
99
+ *
100
+ * 🔴 Why HLS at all, now that Range works. The Range bug (an edge Worker answering 200 + the
101
+ * whole 209 MB to a ranged read) was fixed 2026-08-14 and is NOT what this is for. A single
102
+ * progressive MP4, however well served, cannot ADAPT: a weak cell connection stalls on a
103
+ * 23 Mbps master instead of dropping to a lower rung. Each segment is also a small object that
104
+ * caches WHOLE at the edge, which sidesteps both the 512 MiB single-entry ceiling and the
105
+ * segment-assembly machinery that exists to work around it for big films.
106
+ */
107
+ export const hlsPrefixFor = (key, generation = VIDEO_KEY_GENERATION) => `${key}${videoGenerationTag(generation)}.hls/`;
108
+ /**
109
+ * The playlist a player is pointed at — the one object whose presence means "the ladder is
110
+ * ready". It is written LAST, after every rung, so a consumer that finds it can trust that
111
+ * what it names exists. Absence is normal and means "play the faststart remux".
112
+ */
113
+ export const hlsMasterKeyFor = (key, generation = VIDEO_KEY_GENERATION) => `${hlsPrefixFor(key, generation)}master.m3u8`;
114
+ /**
115
+ * Every derivative suffix, for the "is this key itself a derivative" question.
116
+ *
117
+ * 🔴 Load-bearing in three directions, each of which has already cost something:
118
+ * 1. A derivative must never become the source of another, or a sweep builds
119
+ * `clip.web.mp4.web.mp4` and then `…web.mp4.web.mp4.web.mp4`, forever.
120
+ * 2. A sweep must not count a derivative as work still owed, or the backlog is permanently
121
+ * non-empty and ffmpeg re-runs on every pass.
122
+ * 3. A duplicate scan must not propose a file against its own derivative. They share a title
123
+ * by construction, so every video would pair with its own remux and a review queue would
124
+ * contain nothing else.
125
+ */
126
+ export const DERIVATIVE_SUFFIXES = [
127
+ ".web.mp4",
128
+ ".sprite.jpg",
129
+ ".sprite.vtt",
130
+ ".thumb.jpg",
131
+ ".web.jpg",
132
+ ];
133
+ /**
134
+ * Is this key something binary-server DERIVED rather than something a person uploaded?
135
+ *
136
+ * The HLS tree is matched by its container segment rather than a suffix, because every object
137
+ * inside it (`master.m3u8`, `720/index.m3u8`, `720/000.ts`) is a derivative and none of them
138
+ * ends in a name from the list above.
139
+ */
140
+ const DERIVATIVE_RE = new RegExp(
141
+ // 🔴 The optional `.g<N>` is the encoder generation a video derivative's name carries
142
+ // (see VIDEO_KEY_GENERATION above). Without it, `clip.g2.web.mp4` reads as a SOURCE — and
143
+ // then a sweep derives `clip.g2.web.mp4.web.mp4` from it, for ever. Built from the suffix
144
+ // list rather than written out, so the two cannot drift apart.
145
+ `(?:\\.g\\d+)?(?:${DERIVATIVE_SUFFIXES.map((x) => x.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})$`);
146
+ export const isDerivativeKey = (key) => DERIVATIVE_RE.test(key) ||
147
+ key.includes(".hls/") ||
148
+ /-thumb\.(jpg|webp|png)$/.test(key) ||
149
+ /\.shot\.\d+\.jpg$/.test(key);
@@ -0,0 +1,30 @@
1
+ import type { ProcessingStatus } from 'cursedbelt-core/media';
2
+ import { type MediaStore } from './types';
3
+ export type ProcessProgress = (status: ProcessingStatus, pct: number) => void;
4
+ export type ProcessResult = {
5
+ status: ProcessingStatus;
6
+ durationSeconds: number;
7
+ hasHls: boolean;
8
+ hasSprite: boolean;
9
+ hasPoster: boolean;
10
+ };
11
+ export type ProcessOptions = {
12
+ store: MediaStore;
13
+ /** Scratch directory for ffmpeg inputs/outputs (cleaned up afterwards). */
14
+ workDir: string;
15
+ owner: string;
16
+ id: string;
17
+ /** Storage key of the already-persisted raw source. */
18
+ sourceKey: string;
19
+ /** HLS rung heights to attempt (clamped to the source height; never upscaled). */
20
+ ladderHeights?: number[];
21
+ /**
22
+ * Poster-only mode for memory-constrained hosts: probe duration + extract a single-frame
23
+ * poster (cheap, ~tens of MB peak) and skip the heavy re-encode/HLS/sprite work that OOM'd
24
+ * the 1GB prod box (2026-07-04). The raw source still plays progressively; this only buys a
25
+ * grid thumbnail + duration. Best-effort — a failed poster still lands the row `ready`.
26
+ */
27
+ posterOnly?: boolean;
28
+ onProgress?: ProcessProgress;
29
+ };
30
+ export declare function processVideo(opts: ProcessOptions): Promise<ProcessResult>;