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,231 @@
1
+ import type { Context, Hono } from 'hono';
2
+ import type { SourceRegistry } from './sources';
3
+ import { serveRange } from './streamRange';
4
+ import type { StreamGrant, StreamTokenService } from './streamToken';
5
+ import {
6
+ fsDisplayKey,
7
+ fsHlsFileKey,
8
+ fsMp4Key,
9
+ fsPosterKey,
10
+ fsSourceKey,
11
+ fsSpriteKey,
12
+ fsVttKey,
13
+ type MediaCtx,
14
+ type MediaStore,
15
+ } from './types';
16
+
17
+ // Mounts the byte-serving media routes. These live OUTSIDE an app's guarded API (a <video>/
18
+ // hls.js request can't send the platform Bearer) and authorize each request with a path-embedded
19
+ // stream token instead. Optionally hooks decryption for private (pfs/…) bytes (private-media).
20
+ //
21
+ // `basePath` is the on-server MOUNT path (always path-only, e.g. '/_media'). It is separate from
22
+ // the PUBLIC url base passed to `createMediaUrls` (which may be an absolute API-origin URL for a
23
+ // split-origin deploy); the two only need to share this trailing path. When media is served
24
+ // cross-origin (assets on Cloudflare Pages, API+media on an EC2 subdomain), the browser's media
25
+ // element makes CORS requests — pass `cors` so responses carry the right ACAO and EXPOSE the
26
+ // range headers (Content-Range/Accept-Ranges) a seeking <video> needs cross-origin.
27
+
28
+ /** Resolves the Access-Control-Allow-Origin for a request origin: a fixed origin, an allowlist
29
+ * (echoes the request origin when allowed), or a custom resolver (return null to deny). */
30
+ export type CorsResolver =
31
+ | string
32
+ | string[]
33
+ | ((requestOrigin: string | undefined) => string | null | undefined);
34
+
35
+ export type MountMediaOptions = {
36
+ /** Byte backend shared by this mount (keys carry the owner). */
37
+ store: MediaStore;
38
+ /** Verifies the path token → owner/source/video grant. */
39
+ token: StreamTokenService;
40
+ /** On-server route mount path (path-only; must share its trailing path with the createMediaUrls
41
+ * baseUrl). Default '/_media'. */
42
+ basePath?: string;
43
+ /** Sources, for upload ingest. */
44
+ registry?: SourceRegistry;
45
+ /** Cross-origin access for split-origin deploys (media on a different origin than the SPA).
46
+ * Omit for same-origin. */
47
+ cors?: CorsResolver;
48
+ /** Authorizes an upload request (it CAN send the Bearer, unlike a media element). Return a
49
+ * MediaCtx to allow, or null to reject. Omit ⇒ uploads are disabled on this mount. */
50
+ authorizeUpload?: (c: Context) => Promise<MediaCtx | null> | MediaCtx | null;
51
+ /** Optional hook to decrypt bytes for the private (pfs/…) keyspace before serving. Wired by
52
+ * the private-media layer; omitted ⇒ all bytes served as stored. */
53
+ decryptStream?: (
54
+ stream: ReadableStream<Uint8Array>,
55
+ grant: StreamGrant,
56
+ ) => ReadableStream<Uint8Array>;
57
+ };
58
+
59
+ const EXPOSE_HEADERS = 'Content-Range, Content-Length, Accept-Ranges';
60
+
61
+ function resolveAcao(cors: CorsResolver | undefined, reqOrigin: string | undefined): string | null {
62
+ if (!cors) return null;
63
+ if (typeof cors === 'string') return cors;
64
+ if (Array.isArray(cors)) return reqOrigin && cors.includes(reqOrigin) ? reqOrigin : null;
65
+ return cors(reqOrigin) ?? null;
66
+ }
67
+
68
+ export function mountMediaRoutes(app: Hono, opts: MountMediaOptions): void {
69
+ const base = opts.basePath ?? '/_media';
70
+ const { store, token } = opts;
71
+
72
+ // Stamp CORS headers on a media response (and expose the range headers a cross-origin seeking
73
+ // <video> needs). A no-op when `cors` is unset (same-origin).
74
+ const cors = (c: Context, res: Response): Response => {
75
+ const acao = resolveAcao(opts.cors, c.req.header('origin'));
76
+ if (acao) {
77
+ res.headers.set('Access-Control-Allow-Origin', acao);
78
+ res.headers.set('Access-Control-Expose-Headers', EXPOSE_HEADERS);
79
+ res.headers.append('Vary', 'Origin');
80
+ }
81
+ return res;
82
+ };
83
+
84
+ // Preflight for cross-origin media/upload requests.
85
+ if (opts.cors) {
86
+ app.options(`${base}/*`, (c) => {
87
+ const acao = resolveAcao(opts.cors, c.req.header('origin'));
88
+ const headers: Record<string, string> = {
89
+ 'Access-Control-Allow-Methods': 'GET, PUT, OPTIONS',
90
+ 'Access-Control-Allow-Headers': 'Range, Content-Type, X-Filename, Authorization',
91
+ 'Access-Control-Max-Age': '86400',
92
+ };
93
+ if (acao) {
94
+ headers['Access-Control-Allow-Origin'] = acao;
95
+ headers.Vary = 'Origin';
96
+ }
97
+ return c.body(null, 204, headers);
98
+ });
99
+ }
100
+
101
+ // Resolve + validate the token a serve route carries, and confirm it matches the source/video
102
+ // in the path (so a token minted for video A can't fetch video B).
103
+ const grantFor = async (
104
+ c: Context,
105
+ expectSource: string,
106
+ expectVideo: string,
107
+ ): Promise<StreamGrant | null> => {
108
+ const raw = c.req.param('token');
109
+ if (!raw) return null;
110
+ const grant = await token.verify(raw);
111
+ if (!grant) return null;
112
+ if (grant.sourceId !== expectSource || grant.videoId !== expectVideo) return null;
113
+ return grant;
114
+ };
115
+
116
+ // GET progressive MP4 (Range). Prefers the faststart derivative; falls back to the raw source
117
+ // when the pipeline hasn't produced one (ffmpeg absent / still processing).
118
+ app.get(`${base}/s/:token/media/:source/:video`, async (c) => {
119
+ const grant = await grantFor(c, c.req.param('source'), c.req.param('video'));
120
+ if (!grant) return c.body(null, 401);
121
+ const mp4 = fsMp4Key(grant.owner, grant.videoId);
122
+ const key = (await store.stat(mp4)) ? mp4 : fsSourceKey(grant.owner, grant.videoId);
123
+ if (store.redirectUrl) {
124
+ const url = await store.redirectUrl(key, { mime: 'video/mp4' });
125
+ if (url) return cors(c, c.redirect(url, 303));
126
+ }
127
+ return cors(
128
+ c,
129
+ await serveRange(store, key, 'video/mp4', c.req.header('range'), {
130
+ contentDisposition: 'inline',
131
+ }),
132
+ );
133
+ });
134
+
135
+ // GET an HLS file (master.m3u8 / <height>/playlist.m3u8 / <height>/seg_*.ts). Relative URIs
136
+ // inside the playlists resolve under this same /s/:token/hls/:source/:video/ prefix.
137
+ app.get(`${base}/s/:token/hls/:source/:video/*`, async (c) => {
138
+ const grant = await grantFor(c, c.req.param('source'), c.req.param('video'));
139
+ if (!grant) return c.body(null, 401);
140
+ const prefix = `${base}/s/${c.req.param('token')}/hls/${grant.sourceId}/${grant.videoId}/`;
141
+ const rel = c.req.path.slice(prefix.length);
142
+ if (!rel || rel.includes('..')) return c.body(null, 400);
143
+ const mime = rel.endsWith('.m3u8') ? 'application/vnd.apple.mpegurl' : 'video/mp2t';
144
+ return cors(
145
+ c,
146
+ await serveRange(
147
+ store,
148
+ fsHlsFileKey(grant.owner, grant.videoId, rel),
149
+ mime,
150
+ c.req.header('range'),
151
+ ),
152
+ );
153
+ });
154
+
155
+ app.get(`${base}/s/:token/poster/:source/:video`, async (c) => {
156
+ const grant = await grantFor(c, c.req.param('source'), c.req.param('video'));
157
+ if (!grant) return c.body(null, 401);
158
+ return cors(
159
+ c,
160
+ await serveRange(
161
+ store,
162
+ fsPosterKey(grant.owner, grant.videoId),
163
+ 'image/jpeg',
164
+ c.req.header('range'),
165
+ ),
166
+ );
167
+ });
168
+
169
+ // Full-size browser-safe JPEG for images the browser can't render natively (HEIC/HEIF). Serves
170
+ // the display derivative; falls back to the small poster if it isn't built yet (the boot backfill
171
+ // produces it, so the fallback is momentary) — never a raw HEIC the browser would fail on.
172
+ app.get(`${base}/s/:token/display/:source/:video`, async (c) => {
173
+ const grant = await grantFor(c, c.req.param('source'), c.req.param('video'));
174
+ if (!grant) return c.body(null, 401);
175
+ const displayKey = fsDisplayKey(grant.owner, grant.videoId);
176
+ const key = (await store.stat(displayKey))
177
+ ? displayKey
178
+ : fsPosterKey(grant.owner, grant.videoId);
179
+ return cors(c, await serveRange(store, key, 'image/jpeg', c.req.header('range')));
180
+ });
181
+
182
+ // Sprite sheet + its WebVTT track, served as siblings so the VTT's relative 'sprites.jpg' cue
183
+ // targets resolve to the jpg route.
184
+ app.get(`${base}/s/:token/sprite/:source/:video/:file`, async (c) => {
185
+ const grant = await grantFor(c, c.req.param('source'), c.req.param('video'));
186
+ if (!grant) return c.body(null, 401);
187
+ const file = c.req.param('file');
188
+ if (file === 'sprites.vtt') {
189
+ return cors(
190
+ c,
191
+ await serveRange(
192
+ store,
193
+ fsVttKey(grant.owner, grant.videoId),
194
+ 'text/vtt',
195
+ c.req.header('range'),
196
+ ),
197
+ );
198
+ }
199
+ if (file === 'sprites.jpg') {
200
+ return cors(
201
+ c,
202
+ await serveRange(
203
+ store,
204
+ fsSpriteKey(grant.owner, grant.videoId),
205
+ 'image/jpeg',
206
+ c.req.header('range'),
207
+ ),
208
+ );
209
+ }
210
+ return c.body(null, 404);
211
+ });
212
+
213
+ // PUT upload → ingest. Guarded by the app-supplied authorizer (a fetch CAN send the Bearer).
214
+ const { authorizeUpload, registry } = opts;
215
+ if (authorizeUpload && registry) {
216
+ app.put(`${base}/upload/:source`, async (c) => {
217
+ const ctx = await authorizeUpload(c);
218
+ if (!ctx) return c.json({ error: 'unauthorized' }, 401);
219
+ const source = registry.get(c.req.param('source'));
220
+ if (!source?.ingest) return c.json({ error: 'source not found' }, 404);
221
+ const body = c.req.raw.body;
222
+ if (!body) return c.json({ error: 'empty body' }, 400);
223
+ const video = await source.ingest(ctx, {
224
+ name: c.req.header('x-filename') ?? 'upload',
225
+ mime: c.req.header('content-type') ?? 'application/octet-stream',
226
+ body,
227
+ });
228
+ return c.json({ video });
229
+ });
230
+ }
231
+ }
@@ -0,0 +1,272 @@
1
+ /**
2
+ * `object-store-contract` — ONE suite, both implementations (08 §1 "Gates").
3
+ *
4
+ * The point of running the identical assertions against `createFsObjectStore`
5
+ * (local, over a temp dir) and `createS3ObjectStore` (saas, over an in-process
6
+ * fake bucket implementing the S3 subset) is that `AssetService` and the asset
7
+ * mixin are written against the interface alone. If the two stores disagree on
8
+ * a key, on what "missing" looks like, or on whether a failed upload is
9
+ * visible, then a profile switch is a behavior change — and behavior changes
10
+ * that only appear in production are exactly what a seam is supposed to
11
+ * prevent.
12
+ *
13
+ * The S3 fake is deliberately strict where correctness lives: a multipart
14
+ * upload is only visible after `end()` with NO error, and `end(error)` discards
15
+ * the parts. That is the property that keeps a torn upload from both serving
16
+ * corrupt bytes and accruing invisible storage cost.
17
+ */
18
+ import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
19
+ import { mkdtemp, rm } from 'node:fs/promises';
20
+ import { tmpdir } from 'node:os';
21
+ import { join } from 'node:path';
22
+ import { createFsObjectStore } from 'cwip/storage';
23
+ import type { ObjectStore } from 'cwip/storage';
24
+ import { createS3ObjectStore, type S3ClientLike, type S3ObjectHandle } from './s3ObjectStore';
25
+
26
+ const streamOf = (text: string): ReadableStream<Uint8Array> =>
27
+ new Response(text).body as ReadableStream<Uint8Array>;
28
+
29
+ const textOf = async (body: ReadableStream<Uint8Array> | null): Promise<string | null> =>
30
+ body === null ? null : await new Response(body).text();
31
+
32
+ const tornStream = (): ReadableStream<Uint8Array> =>
33
+ new ReadableStream<Uint8Array>({
34
+ start(controller) {
35
+ controller.enqueue(new TextEncoder().encode('PARTIAL'));
36
+ controller.error(new Error('client hung up'));
37
+ },
38
+ });
39
+
40
+ /* ------------------------------------------------------- the S3 test double */
41
+
42
+ interface FakeBucket extends S3ClientLike {
43
+ objects: Map<string, { body: string; type?: string }>;
44
+ /** Lifetimes (seconds) handed to `presign`, newest last. */
45
+ presigned: number[];
46
+ /** Keys whose multipart upload was aborted rather than completed. */
47
+ aborted: string[];
48
+ }
49
+
50
+ const createFakeBucket = (): FakeBucket => {
51
+ const objects = new Map<string, { body: string; type?: string }>();
52
+ const presigned: number[] = [];
53
+ const aborted: string[] = [];
54
+
55
+ const file = (key: string, options?: { type?: string }): S3ObjectHandle => ({
56
+ stream() {
57
+ const found = objects.get(key);
58
+ if (!found) throw Object.assign(new Error('NoSuchKey'), { code: 'NoSuchKey' });
59
+ return streamOf(found.body);
60
+ },
61
+ writer() {
62
+ // Parts accumulate OFF the bucket; only a clean `end()` publishes them.
63
+ const parts: string[] = [];
64
+ let settled = false;
65
+ return {
66
+ write(chunk: Uint8Array) {
67
+ if (settled) throw new Error('write after end');
68
+ parts.push(new TextDecoder().decode(chunk));
69
+ return chunk.byteLength;
70
+ },
71
+ end(error?: Error) {
72
+ settled = true;
73
+ if (error) {
74
+ aborted.push(key);
75
+ return 0;
76
+ }
77
+ const body = parts.join('');
78
+ objects.set(key, { body, type: options?.type });
79
+ return body.length;
80
+ },
81
+ };
82
+ },
83
+ async exists() {
84
+ return objects.has(key);
85
+ },
86
+ async stat() {
87
+ const found = objects.get(key);
88
+ if (!found) throw Object.assign(new Error('NoSuchKey'), { code: 'NoSuchKey' });
89
+ return { size: found.body.length, type: found.type, lastModified: new Date(0) };
90
+ },
91
+ async delete() {
92
+ if (!objects.delete(key)) throw Object.assign(new Error('NoSuchKey'), { code: 'NoSuchKey' });
93
+ },
94
+ presign(options?: { expiresIn?: number }) {
95
+ presigned.push(options?.expiresIn ?? 0);
96
+ return `https://bucket.example/${key}?X-Amz-Expires=${options?.expiresIn ?? 0}`;
97
+ },
98
+ });
99
+
100
+ return { file, objects, presigned, aborted };
101
+ };
102
+
103
+ /* ------------------------------------------------------------- the contract */
104
+
105
+ interface StoreUnderTest {
106
+ name: string;
107
+ /** Fresh store per test. `lifetimeOf` reads back the expiry a signed URL carries, in seconds. */
108
+ create(): Promise<{ store: ObjectStore; lifetimeOf(url: string): number }>;
109
+ cleanup(): Promise<void>;
110
+ }
111
+
112
+ const runObjectStoreContract = (subject: StoreUnderTest): void => {
113
+ describe(`ObjectStore contract — ${subject.name}`, () => {
114
+ let store: ObjectStore;
115
+ let lifetimeOf: (url: string) => number;
116
+
117
+ beforeEach(async () => {
118
+ ({ store, lifetimeOf } = await subject.create());
119
+ });
120
+ afterEach(async () => {
121
+ await subject.cleanup();
122
+ });
123
+
124
+ it('round-trips bytes, size and content type', async () => {
125
+ const stat = await store.put('t_1/file_a', streamOf('HELLO'), { contentType: 'text/plain' });
126
+ expect(stat).toMatchObject({ key: 't_1/file_a', size: 5, contentType: 'text/plain' });
127
+
128
+ await expect(textOf(await store.get('t_1/file_a'))).resolves.toBe('HELLO');
129
+ await expect(store.stat('t_1/file_a')).resolves.toMatchObject({ size: 5, contentType: 'text/plain' });
130
+ });
131
+
132
+ it('reports a missing object as null on BOTH read paths', async () => {
133
+ await expect(store.get('t_1/absent')).resolves.toBeNull();
134
+ await expect(store.stat('t_1/absent')).resolves.toBeNull();
135
+ });
136
+
137
+ it('refuses the same illegal keys on both profiles', async () => {
138
+ for (const key of ['t_1/../escape', '/absolute', 't_1/.hidden', 't_1//empty', 't_1/sp ace']) {
139
+ await expect(store.put(key, streamOf('x'))).rejects.toMatchObject({ code: 'OBJECT_KEY_INVALID' });
140
+ }
141
+ });
142
+
143
+ it('never publishes a torn upload', async () => {
144
+ await expect(store.put('t_1/file_torn', tornStream())).rejects.toThrow('client hung up');
145
+ await expect(store.get('t_1/file_torn')).resolves.toBeNull();
146
+ await expect(store.stat('t_1/file_torn')).resolves.toBeNull();
147
+ });
148
+
149
+ it('overwrites cleanly rather than appending', async () => {
150
+ await store.put('t_1/file_a', streamOf('FIRST'), { contentType: 'text/plain' });
151
+ const second = await store.put('t_1/file_a', streamOf('SECOND-AND-LONGER'), { contentType: 'text/plain' });
152
+ expect(second.size).toBe('SECOND-AND-LONGER'.length);
153
+ await expect(textOf(await store.get('t_1/file_a'))).resolves.toBe('SECOND-AND-LONGER');
154
+ });
155
+
156
+ it('delete is idempotent and reports whether anything was removed', async () => {
157
+ await store.put('t_1/file_a', streamOf('x'));
158
+ await expect(store.delete('t_1/file_a')).resolves.toBe(true);
159
+ await expect(store.delete('t_1/file_a')).resolves.toBe(false);
160
+ await expect(store.get('t_1/file_a')).resolves.toBeNull();
161
+ });
162
+
163
+ it('streams a multi-chunk body without reassembling it upstream', async () => {
164
+ const chunks = ['alpha', 'beta', 'gamma'];
165
+ const body = new ReadableStream<Uint8Array>({
166
+ start(controller) {
167
+ for (const chunk of chunks) controller.enqueue(new TextEncoder().encode(chunk));
168
+ controller.close();
169
+ },
170
+ });
171
+ const stat = await store.put('t_1/file_multi', body);
172
+ expect(stat.size).toBe('alphabetagamma'.length);
173
+ await expect(textOf(await store.get('t_1/file_multi'))).resolves.toBe('alphabetagamma');
174
+ });
175
+
176
+ it('issues a signed URL with an expiry, and caps a caller asking for a year', async () => {
177
+ await store.put('t_1/file_a', streamOf('x'));
178
+
179
+ const shortLived = await store.signedUrl('t_1/file_a', { expiresInSeconds: 60 });
180
+ expect(shortLived).toContain('t_1/file_a');
181
+ expect(lifetimeOf(shortLived)).toBe(60);
182
+
183
+ const capped = await store.signedUrl('t_1/file_a', { expiresInSeconds: 31_536_000 });
184
+ expect(lifetimeOf(capped)).toBe(86_400);
185
+
186
+ const defaulted = await store.signedUrl('t_1/file_a');
187
+ expect(lifetimeOf(defaulted)).toBe(300);
188
+ });
189
+
190
+ it('refuses a nonsensical signed-url lifetime instead of minting a permanent URL', async () => {
191
+ await expect(store.signedUrl('t_1/file_a', { expiresInSeconds: 0 })).rejects.toMatchObject({
192
+ code: 'OBJECT_KEY_INVALID',
193
+ });
194
+ await expect(store.signedUrl('t_1/file_a', { expiresInSeconds: -1 })).rejects.toBeDefined();
195
+ });
196
+ });
197
+ };
198
+
199
+ /* ------------------------------------------------------------ both subjects */
200
+
201
+ let fsRoot: string | null = null;
202
+
203
+ runObjectStoreContract({
204
+ name: 'createFsObjectStore (local)',
205
+ async create() {
206
+ fsRoot = await mkdtemp(join(tmpdir(), 'cc-object-store-contract-'));
207
+ const store = createFsObjectStore({ root: fsRoot, signingKey: 'contract-key', now: () => 1_000_000 });
208
+ return {
209
+ store,
210
+ lifetimeOf: (url) => Number(new URL(url, 'http://local').searchParams.get('exp')) - 1_000_000,
211
+ };
212
+ },
213
+ async cleanup() {
214
+ if (fsRoot) await rm(fsRoot, { recursive: true, force: true });
215
+ fsRoot = null;
216
+ },
217
+ });
218
+
219
+ let bucket: FakeBucket | null = null;
220
+
221
+ runObjectStoreContract({
222
+ name: 'createS3ObjectStore (saas)',
223
+ async create() {
224
+ bucket = createFakeBucket();
225
+ const store = createS3ObjectStore({ client: bucket });
226
+ return { store, lifetimeOf: (url) => Number(new URL(url).searchParams.get('X-Amz-Expires')) };
227
+ },
228
+ async cleanup() {
229
+ bucket = null;
230
+ },
231
+ });
232
+
233
+ /* --------------------------------------------------- saas-only expectations */
234
+
235
+ describe('createS3ObjectStore specifics', () => {
236
+ it('aborts the multipart upload on failure rather than completing it', async () => {
237
+ const fake = createFakeBucket();
238
+ const store = createS3ObjectStore({ client: fake });
239
+
240
+ await expect(store.put('t_1/file_torn', tornStream())).rejects.toThrow('client hung up');
241
+ // Abort, not complete: an abandoned multipart bills for parts nobody can list.
242
+ expect(fake.aborted).toEqual(['t_1/file_torn']);
243
+ expect(fake.objects.has('t_1/file_torn')).toBe(false);
244
+ });
245
+
246
+ it('namespaces every physical key under keyPrefix while the logical key is unchanged', async () => {
247
+ const fake = createFakeBucket();
248
+ const store = createS3ObjectStore({ client: fake, keyPrefix: '/apps/demo/' });
249
+
250
+ await store.put('t_1/file_a', streamOf('HELLO'), { contentType: 'text/plain' });
251
+ expect([...fake.objects.keys()]).toEqual(['apps/demo/t_1/file_a']);
252
+ // The caller still speaks the tenant-scoped logical key — the prefix is
253
+ // deployment topology, invisible above the store.
254
+ await expect(textOf(await store.get('t_1/file_a'))).resolves.toBe('HELLO');
255
+ await expect(store.stat('t_1/file_a')).resolves.toMatchObject({ key: 't_1/file_a', size: 5 });
256
+ await expect(store.delete('t_1/file_a')).resolves.toBe(true);
257
+ });
258
+
259
+ it('presigns a GET, so the bytes leave via the CDN rather than this process', async () => {
260
+ const fake = createFakeBucket();
261
+ const store = createS3ObjectStore({ client: fake });
262
+ await store.put('t_1/file_a', streamOf('x'));
263
+
264
+ const url = await store.signedUrl('t_1/file_a');
265
+ expect(url.startsWith('https://')).toBe(true);
266
+ expect(fake.presigned.at(-1)).toBe(300);
267
+ });
268
+
269
+ it('refuses to construct without a client or a bucket', () => {
270
+ expect(() => createS3ObjectStore({})).toThrow(/client.*bucket|bucket/i);
271
+ });
272
+ });
@@ -0,0 +1,63 @@
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
+
13
+ import type { ObjectStore } from 'cwip/storage';
14
+ import type { MediaStore } from './types';
15
+
16
+ const streamToBytes = async (stream: ReadableStream<Uint8Array>): Promise<Uint8Array> =>
17
+ new Uint8Array(await new Response(stream).arrayBuffer());
18
+
19
+ const bytesToStream = (bytes: Uint8Array): ReadableStream<Uint8Array> =>
20
+ new ReadableStream<Uint8Array>({
21
+ start(controller) {
22
+ controller.enqueue(bytes);
23
+ controller.close();
24
+ },
25
+ });
26
+
27
+ /** Adapt an {@link ObjectStore} to the {@link MediaStore} contract `processVideo` expects. */
28
+ export function objectStoreAsMediaStore(objects: ObjectStore): MediaStore {
29
+ return {
30
+ async put(key, bytes, mime) {
31
+ await objects.put(key, bytesToStream(bytes), { contentType: mime });
32
+ },
33
+ async putStream(key, body, mime) {
34
+ await objects.put(key, body, { contentType: mime });
35
+ },
36
+ async get(key) {
37
+ const stream = await objects.get(key);
38
+ return stream ? await streamToBytes(stream) : null;
39
+ },
40
+ async remove(key) {
41
+ await objects.delete(key);
42
+ },
43
+ async removePrefix() {
44
+ // The `ObjectStore` contract has no prefix delete, and `processVideo` never
45
+ // calls this — surface misuse loudly rather than silently no-op.
46
+ throw new Error('objectStoreAsMediaStore.removePrefix is not supported over an ObjectStore.');
47
+ },
48
+ async stat(key) {
49
+ const stat = await objects.stat(key);
50
+ return stat ? { size: stat.size } : null;
51
+ },
52
+ async getRange(key, range) {
53
+ const stream = await objects.get(key);
54
+ if (!stream) return null;
55
+ if (!range) return { stream };
56
+ // Whole-object read then slice: unused by `processVideo`, present only to
57
+ // satisfy the interface. A hot Range-serving path uses the real store.
58
+ const bytes = await streamToBytes(stream);
59
+ return { stream: bytesToStream(bytes.subarray(range.start, range.end + 1)) };
60
+ },
61
+ // No `localPath`: force `processVideo` down its download path (stream store).
62
+ };
63
+ }