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,63 @@
1
+ import type { ObjectStore } from 'cwip/storage';
2
+ /**
3
+ * The slice of `Bun.S3File` this store uses.
4
+ *
5
+ * Declared structurally rather than imported as `Bun.S3File` so the contract
6
+ * suite can drive BOTH implementations against an in-process fake bucket. A
7
+ * store whose only proof is "it compiles against the real SDK types" is a store
8
+ * whose behavior has never been tested.
9
+ */
10
+ export interface S3ObjectHandle {
11
+ stream(): ReadableStream<Uint8Array>;
12
+ writer(options?: {
13
+ partSize?: number;
14
+ queueSize?: number;
15
+ retry?: number;
16
+ }): S3ObjectWriter;
17
+ exists(): Promise<boolean>;
18
+ stat(): Promise<{
19
+ size: number;
20
+ type?: string;
21
+ lastModified?: Date;
22
+ etag?: string;
23
+ }>;
24
+ delete(): Promise<void>;
25
+ presign(options?: {
26
+ expiresIn?: number;
27
+ method?: 'GET';
28
+ }): string;
29
+ }
30
+ export interface S3ObjectWriter {
31
+ write(chunk: Uint8Array): number | Promise<number>;
32
+ /** Passing an error aborts the multipart upload instead of completing it. */
33
+ end(error?: Error): number | Promise<number>;
34
+ }
35
+ export interface S3ClientLike {
36
+ file(key: string, options?: {
37
+ type?: string;
38
+ }): S3ObjectHandle;
39
+ }
40
+ export interface S3ObjectStoreOptions {
41
+ /** Pre-built client. Supply this OR the credential fields below. */
42
+ client?: S3ClientLike;
43
+ bucket?: string;
44
+ /** e.g. `https://<account>.r2.cloudflarestorage.com`. */
45
+ endpoint?: string;
46
+ region?: string;
47
+ accessKeyId?: string;
48
+ secretAccessKey?: string;
49
+ sessionToken?: string;
50
+ /**
51
+ * Namespace every key under this prefix, so ONE bucket can host many apps
52
+ * without collisions. Tenant scoping stays in the key shape (`<tenant>/<file>`)
53
+ * — the prefix is deployment topology, not a security boundary.
54
+ */
55
+ keyPrefix?: string;
56
+ /** Multipart part size. Default 8MiB. */
57
+ partSize?: number;
58
+ /** Default presigned-URL lifetime in seconds. */
59
+ defaultExpiresInSeconds?: number;
60
+ /** Ceiling on a caller-requested lifetime. */
61
+ maxExpiresInSeconds?: number;
62
+ }
63
+ export declare function createS3ObjectStore(options: S3ObjectStoreOptions): ObjectStore;
@@ -0,0 +1,187 @@
1
+ /**
2
+ * `createS3ObjectStore` — the SAAS profile's binary tier (08 §1b, 01 §2.5).
3
+ *
4
+ * The companion to `createFsObjectStore` (cwip): same `ObjectStore` contract,
5
+ * different issuer. Everything above this line — `AssetService`, the asset
6
+ * mixin, the client — is written against the interface, so switching profiles
7
+ * swaps this factory in and changes nothing else. That is the whole reason the
8
+ * local store bothers to sign its URLs.
9
+ *
10
+ * Built on `Bun.S3Client`, which ships in the runtime cursedbelt already
11
+ * requires — no `aws-sdk`, no transitive credential-chain surprises. Works
12
+ * against S3, R2, MinIO or any other S3-compatible endpoint.
13
+ *
14
+ * Three things this does that a naive `PutObject` wrapper does not:
15
+ *
16
+ * * **Streams through a multipart writer.** A 4GB upload is fed part-by-part
17
+ * (default 8MiB) straight from the request stream. Nothing buffers the
18
+ * object, so request memory is O(part), not O(file).
19
+ * * **Aborts the multipart on failure.** `NetworkSink.end(error)` tells Bun to
20
+ * abort rather than complete the upload; an abandoned multipart otherwise
21
+ * accrues storage cost that never appears in a bucket listing — an invisible
22
+ * bill, which is the worst kind.
23
+ * * **Presigns for real.** `signedUrl` returns a genuine presigned GET so the
24
+ * bytes leave via the CDN/edge, not through the app process. The asset
25
+ * mixin's bytes routes stay as the fallback for private/derived content.
26
+ *
27
+ * Credentials are OPTIONS, never `process.env`: the host resolves them from
28
+ * GuardEngine secrets or its deploy environment (01 §3.3) and injects them, so
29
+ * this module has no ambient authority and a test can hand it a fake client.
30
+ */
31
+ import { assertObjectKey } from 'cwip/storage';
32
+ import { AppError } from '../errors';
33
+ const DEFAULT_PART_SIZE = 8 * 1024 * 1024;
34
+ const DEFAULT_EXPIRES_IN_SECONDS = 300;
35
+ const MAX_EXPIRES_IN_SECONDS = 86_400;
36
+ const buildClient = (options) => {
37
+ if (options.client)
38
+ return options.client;
39
+ if (!options.bucket)
40
+ throw new AppError('createS3ObjectStore needs either a `client` or a `bucket` (plus credentials).', {
41
+ code: 'OBJECT_STORE_MISCONFIGURED',
42
+ status: 500,
43
+ category: 'storage',
44
+ isOperational: false,
45
+ });
46
+ const S3Client = Bun.S3Client;
47
+ if (typeof S3Client !== 'function')
48
+ throw new AppError('This Bun runtime has no S3Client; pass a `client` implementing the S3 subset instead.', {
49
+ code: 'OBJECT_STORE_MISCONFIGURED',
50
+ status: 500,
51
+ category: 'storage',
52
+ isOperational: false,
53
+ });
54
+ return new S3Client({
55
+ bucket: options.bucket,
56
+ endpoint: options.endpoint,
57
+ region: options.region,
58
+ accessKeyId: options.accessKeyId,
59
+ secretAccessKey: options.secretAccessKey,
60
+ sessionToken: options.sessionToken,
61
+ });
62
+ };
63
+ export function createS3ObjectStore(options) {
64
+ const client = buildClient(options);
65
+ const partSize = options.partSize ?? DEFAULT_PART_SIZE;
66
+ const defaultExpiry = options.defaultExpiresInSeconds ?? DEFAULT_EXPIRES_IN_SECONDS;
67
+ const maxExpiry = options.maxExpiresInSeconds ?? MAX_EXPIRES_IN_SECONDS;
68
+ const prefix = options.keyPrefix ? `${options.keyPrefix.replace(/^\/+|\/+$/g, '')}/` : '';
69
+ /**
70
+ * The SAME key whitelist the fs store enforces.
71
+ *
72
+ * S3 has no directories, so traversal is not the risk here — divergence is. A
73
+ * key that one profile accepts and the other refuses turns a profile switch
74
+ * into a data-loss migration, so both stores validate identically and the
75
+ * contract suite asserts it.
76
+ */
77
+ const physical = (key) => {
78
+ assertObjectKey(key);
79
+ return `${prefix}${key}`;
80
+ };
81
+ const statOf = async (key) => {
82
+ const handle = client.file(physical(key));
83
+ try {
84
+ const info = await handle.stat();
85
+ return {
86
+ key,
87
+ size: info.size,
88
+ contentType: info.type,
89
+ modifiedAt: (info.lastModified ?? new Date()).toISOString(),
90
+ };
91
+ }
92
+ catch (error) {
93
+ if (isNotFound(error))
94
+ return null;
95
+ throw error;
96
+ }
97
+ };
98
+ return {
99
+ async put(key, body, putOptions) {
100
+ const contentType = putOptions?.contentType;
101
+ const handle = client.file(physical(key), contentType ? { type: contentType } : undefined);
102
+ const writer = handle.writer({ partSize });
103
+ const reader = body.getReader();
104
+ let size = 0;
105
+ try {
106
+ while (true) {
107
+ const { done, value } = await reader.read();
108
+ if (done)
109
+ break;
110
+ if (value.byteLength === 0)
111
+ continue;
112
+ await writer.write(value);
113
+ size += value.byteLength;
114
+ }
115
+ await writer.end();
116
+ }
117
+ catch (error) {
118
+ // Abort, don't complete. Best-effort: if the abort itself fails there is
119
+ // nothing further this process can do, and swallowing it would mask the
120
+ // ORIGINAL upload failure, which is the one worth reporting.
121
+ await Promise.resolve(writer.end(error instanceof Error ? error : new Error(String(error)))).catch(() => { });
122
+ throw error;
123
+ }
124
+ finally {
125
+ reader.releaseLock();
126
+ }
127
+ return { key, size, contentType, modifiedAt: new Date().toISOString() };
128
+ },
129
+ async get(key) {
130
+ const handle = client.file(physical(key));
131
+ if (!(await handle.exists()))
132
+ return null;
133
+ try {
134
+ return handle.stream();
135
+ }
136
+ catch (error) {
137
+ // Raced with a delete between `exists` and `stream` — a missing object
138
+ // is `null`, not a 500.
139
+ if (isNotFound(error))
140
+ return null;
141
+ throw error;
142
+ }
143
+ },
144
+ stat: statOf,
145
+ async delete(key) {
146
+ const existed = (await statOf(key)) !== null;
147
+ try {
148
+ await client.file(physical(key)).delete();
149
+ }
150
+ catch (error) {
151
+ if (!isNotFound(error))
152
+ throw error;
153
+ }
154
+ return existed;
155
+ },
156
+ async signedUrl(key, urlOptions) {
157
+ const requested = urlOptions?.expiresInSeconds ?? defaultExpiry;
158
+ if (!Number.isFinite(requested) || requested <= 0)
159
+ throw new AppError(`Signed-url lifetime for '${key}' must be a positive number of seconds.`, {
160
+ code: 'OBJECT_KEY_INVALID',
161
+ status: 400,
162
+ category: 'storage',
163
+ context: { key, requested },
164
+ });
165
+ return client
166
+ .file(physical(key))
167
+ .presign({ expiresIn: Math.min(Math.floor(requested), maxExpiry), method: 'GET' });
168
+ },
169
+ };
170
+ }
171
+ /**
172
+ * Is this the store telling us the object is not there?
173
+ *
174
+ * S3-compatible endpoints disagree on the shape (`NoSuchKey`, `NotFound`, a
175
+ * bare 404), and Bun surfaces its own `code`. Matching all of them keeps a
176
+ * missing object a `null` on every backend rather than a 500 on MinIO and a
177
+ * `null` on R2.
178
+ */
179
+ const isNotFound = (error) => {
180
+ if (!error || typeof error !== 'object')
181
+ return false;
182
+ const candidate = error;
183
+ const code = String(candidate.code ?? candidate.name ?? '');
184
+ if (/NoSuchKey|NotFound|ENOENT|ERR_S3_FILE_NOT_FOUND/i.test(code))
185
+ return true;
186
+ return candidate.status === 404 || candidate.statusCode === 404;
187
+ };
@@ -0,0 +1,47 @@
1
+ import type { ProcessingStatus } from 'cursedbelt-core/media';
2
+ import type { StreamTokenService } from './streamToken';
3
+ import { type MediaCtx, type MediaSource, type MediaStore } from './types';
4
+ export interface MediaUrls {
5
+ build(ctx: MediaCtx, sourceId: string, videoId: string): Promise<{
6
+ streamUrl: string;
7
+ hlsUrl: string;
8
+ posterUrl: string;
9
+ displayUrl: string;
10
+ vttUrl: string;
11
+ spriteUrl: string;
12
+ }>;
13
+ }
14
+ export declare function createMediaUrls(opts: {
15
+ /** Public base for browser media URLs. Path-only ('/_media') for same-origin, or an absolute
16
+ * origin+path ('https://api.example.com/_media') for a split-origin deploy. Must share its
17
+ * trailing path with `mountMediaRoutes`' `basePath`. */
18
+ baseUrl: string;
19
+ token: StreamTokenService;
20
+ /** Owner keyspace segment for a request. Defaults to tenantId (a tenant-shared gallery). */
21
+ owner?: (ctx: MediaCtx) => string;
22
+ }): MediaUrls;
23
+ export declare class SourceRegistry {
24
+ private sources;
25
+ register(source: MediaSource): this;
26
+ get(id: string): MediaSource | undefined;
27
+ list(): MediaSource[];
28
+ }
29
+ export type DiskSource = MediaSource & {
30
+ /** Read current processing status (for the gallery's status poll / tests). */
31
+ status(ctx: MediaCtx, videoId: string): {
32
+ status: ProcessingStatus;
33
+ progress: number;
34
+ } | null;
35
+ };
36
+ export declare function createDiskSource(cfg: {
37
+ id: string;
38
+ title: string;
39
+ store: MediaStore;
40
+ /** ffmpeg scratch dir. */
41
+ workDir: string;
42
+ urls: MediaUrls;
43
+ /** Owner keyspace segment; defaults to tenantId. */
44
+ owner?: (ctx: MediaCtx) => string;
45
+ /** Injectable clock (tests). */
46
+ now?: () => number;
47
+ }): DiskSource;
@@ -0,0 +1,148 @@
1
+ import { fsMp4Key, fsPosterKey, fsSourceKey, fsSpriteKey, fsVttKey, } from './types';
2
+ import { processVideo } from './videoProcessor';
3
+ export function createMediaUrls(opts) {
4
+ const ownerOf = opts.owner ?? ((c) => c.tenantId);
5
+ // Trim a trailing slash so 'https://api…/_media/' and '/_media' behave identically.
6
+ const base = opts.baseUrl.replace(/\/$/, '');
7
+ return {
8
+ async build(ctx, sourceId, videoId) {
9
+ const token = await opts.token.sign({ owner: ownerOf(ctx), sourceId, videoId });
10
+ const p = `${base}/s/${token}`;
11
+ return {
12
+ streamUrl: `${p}/media/${sourceId}/${videoId}`,
13
+ hlsUrl: `${p}/hls/${sourceId}/${videoId}/master.m3u8`,
14
+ posterUrl: `${p}/poster/${sourceId}/${videoId}`,
15
+ displayUrl: `${p}/display/${sourceId}/${videoId}`,
16
+ vttUrl: `${p}/sprite/${sourceId}/${videoId}/sprites.vtt`,
17
+ spriteUrl: `${p}/sprite/${sourceId}/${videoId}/sprites.jpg`,
18
+ };
19
+ },
20
+ };
21
+ }
22
+ // ── Source registry ───────────────────────────────────────────────────────────────
23
+ export class SourceRegistry {
24
+ sources = new Map();
25
+ register(source) {
26
+ this.sources.set(source.id, source);
27
+ return this;
28
+ }
29
+ get(id) {
30
+ return this.sources.get(id);
31
+ }
32
+ list() {
33
+ return [...this.sources.values()];
34
+ }
35
+ }
36
+ export function createDiskSource(cfg) {
37
+ const ownerOf = cfg.owner ?? ((c) => c.tenantId);
38
+ const now = cfg.now ?? (() => Date.now());
39
+ const byTenant = new Map();
40
+ const bucket = (ctx) => {
41
+ let m = byTenant.get(ctx.tenantId);
42
+ if (!m) {
43
+ m = new Map();
44
+ byTenant.set(ctx.tenantId, m);
45
+ }
46
+ return m;
47
+ };
48
+ const toPlayable = async (ctx, meta) => {
49
+ const u = await cfg.urls.build(ctx, cfg.id, meta.id);
50
+ return {
51
+ id: meta.id,
52
+ name: meta.name,
53
+ streamUrl: u.streamUrl,
54
+ hlsUrl: meta.hasHls ? u.hlsUrl : undefined,
55
+ posterUrl: meta.hasPoster ? u.posterUrl : undefined,
56
+ vttUrl: meta.hasSprite ? u.vttUrl : undefined,
57
+ spriteUrl: meta.hasSprite ? u.spriteUrl : undefined,
58
+ durationSeconds: meta.durationSeconds,
59
+ tags: meta.tags,
60
+ processingStatus: meta.status,
61
+ processingProgress: meta.progress,
62
+ };
63
+ };
64
+ return {
65
+ id: cfg.id,
66
+ title: cfg.title,
67
+ async list(ctx, opts) {
68
+ const all = [...bucket(ctx).values()].sort((a, b) => b.createdAt - a.createdAt);
69
+ const q = opts.query?.trim().toLowerCase();
70
+ const filtered = all.filter((m) => (!opts.tag || m.tags.includes(opts.tag)) &&
71
+ (!q || m.name.toLowerCase().includes(q) || m.tags.some((t) => t.includes(q))));
72
+ const limit = opts.limit ?? 24;
73
+ const start = opts.cursor ? Number(opts.cursor) || 0 : 0;
74
+ const page = filtered.slice(start, start + limit);
75
+ const items = await Promise.all(page.map((m) => toPlayable(ctx, m)));
76
+ const next = start + limit;
77
+ return { items, nextCursor: next < filtered.length ? String(next) : undefined };
78
+ },
79
+ async resolve(ctx, videoId) {
80
+ const meta = bucket(ctx).get(videoId);
81
+ return meta ? toPlayable(ctx, meta) : null;
82
+ },
83
+ async tags(ctx) {
84
+ const set = new Set();
85
+ for (const m of bucket(ctx).values()) {
86
+ for (const t of m.tags)
87
+ set.add(t);
88
+ }
89
+ return [...set].sort();
90
+ },
91
+ async ingest(ctx, upload) {
92
+ const owner = ownerOf(ctx);
93
+ const id = crypto.randomUUID();
94
+ await cfg.store.putStream(fsSourceKey(owner, id), upload.body, upload.mime, {
95
+ contentLength: upload.size,
96
+ });
97
+ const meta = {
98
+ id,
99
+ name: upload.name,
100
+ tags: [],
101
+ mime: upload.mime,
102
+ status: 'processing',
103
+ progress: 0,
104
+ hasHls: false,
105
+ hasSprite: false,
106
+ hasPoster: false,
107
+ createdAt: now(),
108
+ };
109
+ bucket(ctx).set(id, meta);
110
+ // Fire-and-forget the pipeline; the client polls status via the source's status()/list().
111
+ void processVideo({
112
+ store: cfg.store,
113
+ workDir: cfg.workDir,
114
+ owner,
115
+ id,
116
+ sourceKey: fsSourceKey(owner, id),
117
+ onProgress: (status, pct) => {
118
+ meta.status = status;
119
+ meta.progress = pct;
120
+ },
121
+ }).then((res) => {
122
+ meta.status = res.status;
123
+ meta.progress = res.status === 'ready' ? 100 : meta.progress;
124
+ meta.durationSeconds = res.durationSeconds || meta.durationSeconds;
125
+ meta.hasHls = res.hasHls;
126
+ meta.hasSprite = res.hasSprite;
127
+ meta.hasPoster = res.hasPoster;
128
+ });
129
+ return toPlayable(ctx, meta);
130
+ },
131
+ async remove(ctx, videoId) {
132
+ const owner = ownerOf(ctx);
133
+ await Promise.all([
134
+ cfg.store.remove(fsSourceKey(owner, videoId)),
135
+ cfg.store.remove(fsMp4Key(owner, videoId)),
136
+ cfg.store.remove(fsPosterKey(owner, videoId)),
137
+ cfg.store.remove(fsSpriteKey(owner, videoId)),
138
+ cfg.store.remove(fsVttKey(owner, videoId)),
139
+ cfg.store.removePrefix(`hls/${owner}/${videoId}`),
140
+ ]);
141
+ bucket(ctx).delete(videoId);
142
+ },
143
+ status(ctx, videoId) {
144
+ const meta = bucket(ctx).get(videoId);
145
+ return meta ? { status: meta.status, progress: meta.progress } : null;
146
+ },
147
+ };
148
+ }
@@ -0,0 +1,19 @@
1
+ import type { MediaStore } from './types';
2
+ export type RangeDecision = {
3
+ kind: 'full';
4
+ } | {
5
+ kind: 'partial';
6
+ start: number;
7
+ end: number;
8
+ } | {
9
+ kind: 'unsatisfiable';
10
+ };
11
+ export declare function decodeRange(header: string | undefined | null, total: number): RangeDecision;
12
+ export type ServeRangeOpts = {
13
+ /** Content-Disposition value, e.g. 'inline' or `attachment; filename="clip.mp4"`. */
14
+ contentDisposition?: string;
15
+ /** Overrides the default 'private, max-age=86400' (e.g. 'no-store' for private bytes). */
16
+ cacheControl?: string;
17
+ extraHeaders?: Record<string, string>;
18
+ };
19
+ export declare function serveRange(store: MediaStore, key: string, mime: string, rangeHeader: string | undefined | null, opts?: ServeRangeOpts): Promise<Response>;
@@ -0,0 +1,69 @@
1
+ // Parse a single-range `Range: bytes=start-end` header against a known total size. Only the
2
+ // common single-range forms are supported (`a-b`, `a-`, `-n`); anything malformed falls back
3
+ // to a full response, and an out-of-bounds range is rejected so the caller can answer 416.
4
+ // End is clamped to the last byte; ranges are inclusive.
5
+ export function decodeRange(header, total) {
6
+ if (!header)
7
+ return { kind: 'full' };
8
+ const match = /^bytes=(\d*)-(\d*)$/.exec(header.trim());
9
+ if (!match)
10
+ return { kind: 'full' };
11
+ const [, rawStart, rawEnd] = match;
12
+ if (rawStart === '' && rawEnd === '')
13
+ return { kind: 'full' };
14
+ let start;
15
+ let end;
16
+ if (rawStart === '') {
17
+ // Suffix range: the last N bytes.
18
+ const suffix = Number.parseInt(rawEnd, 10);
19
+ if (suffix <= 0)
20
+ return { kind: 'unsatisfiable' };
21
+ start = Math.max(0, total - suffix);
22
+ end = total - 1;
23
+ }
24
+ else {
25
+ start = Number.parseInt(rawStart, 10);
26
+ end = rawEnd === '' ? total - 1 : Number.parseInt(rawEnd, 10);
27
+ }
28
+ if (start > end || start >= total || start < 0)
29
+ return { kind: 'unsatisfiable' };
30
+ if (end >= total)
31
+ end = total - 1;
32
+ return { kind: 'partial', start, end };
33
+ }
34
+ // Build a Range-aware Response for a stored object. `rangeHeader` is the request's Range header
35
+ // (Hono: `c.req.header('range')`). 404 if the key is gone, 416 for an unsatisfiable range, 206
36
+ // for a partial hit, else 200.
37
+ export async function serveRange(store, key, mime, rangeHeader, opts = {}) {
38
+ const meta = await store.stat(key);
39
+ if (!meta)
40
+ return new Response(null, { status: 404 });
41
+ const total = meta.size;
42
+ const headers = new Headers({
43
+ 'Accept-Ranges': 'bytes',
44
+ 'Content-Type': mime,
45
+ 'Cache-Control': opts.cacheControl ?? 'private, max-age=86400',
46
+ });
47
+ if (opts.contentDisposition)
48
+ headers.set('Content-Disposition', opts.contentDisposition);
49
+ if (opts.extraHeaders) {
50
+ for (const [k, v] of Object.entries(opts.extraHeaders))
51
+ headers.set(k, v);
52
+ }
53
+ const decision = decodeRange(rangeHeader, total);
54
+ if (decision.kind === 'unsatisfiable') {
55
+ headers.set('Content-Range', `bytes */${total}`);
56
+ return new Response(null, { status: 416, headers });
57
+ }
58
+ const range = decision.kind === 'partial' ? decision : undefined;
59
+ const result = await store.getRange(key, range);
60
+ if (!result)
61
+ return new Response(null, { status: 404 });
62
+ if (range) {
63
+ headers.set('Content-Range', `bytes ${range.start}-${range.end}/${total}`);
64
+ headers.set('Content-Length', String(range.end - range.start + 1));
65
+ return new Response(result.stream, { status: 206, headers });
66
+ }
67
+ headers.set('Content-Length', String(total));
68
+ return new Response(result.stream, { status: 200, headers });
69
+ }
@@ -0,0 +1,16 @@
1
+ /** The scope a stream token authorizes: one video's whole derivative tree, under one owner. */
2
+ export type StreamGrant = {
3
+ /** owner keyspace segment (the media lives under fs/<owner>/… , hls/<owner>/… , …). */
4
+ owner: string;
5
+ sourceId: string;
6
+ videoId: string;
7
+ };
8
+ export interface StreamTokenService {
9
+ sign(grant: StreamGrant, ttlSeconds?: number): Promise<string>;
10
+ verify(token: string): Promise<StreamGrant | null>;
11
+ }
12
+ export declare function createStreamTokenService(opts: {
13
+ secret: string;
14
+ /** Default 6h — long enough to survive a paused-and-resumed playback without a 401. */
15
+ defaultTtlSeconds?: number;
16
+ }): StreamTokenService;
@@ -0,0 +1,54 @@
1
+ // 🔴 `../auth/jwt`, NOT `../auth`. The jose half is a REAL dependency — stream tokens are
2
+ // signed and verified here, and jose is a plain `dependencies` entry every consumer already
3
+ // installs. The BARREL was the accident: `../auth/index.ts` also reaches `totp.ts` → `otplib`,
4
+ // an OPTIONAL peer, so every app that touched anything in `cursedbelt-server/storage` was made
5
+ // to install a one-time-password library for code it never calls. Import the leaf module that
6
+ // has what you need; never the index that has everything. `src/leafSubpathsImportNothing.spec.ts`
7
+ // fails if this file (or anything else the storage barrel reaches) undoes it.
8
+ import { signToken, verifyPurposeToken } from '../auth/jwt';
9
+ // ── Stream tokens ───────────────────────────────────────────────────────────────
10
+ // Media bytes are loaded by <video>/hls.js/<img>, which cannot send the platform Bearer
11
+ // header an app's auth uses. So byte-serving routes live OUTSIDE the guarded API and
12
+ // authorize each request with a short-lived, PATH-embedded token instead.
13
+ //
14
+ // Why path-embedded, not ?t=: hls.js resolves segment/playlist URIs RELATIVE to the master
15
+ // playlist and does NOT inherit query strings — a `?t=` token would be dropped on segment
16
+ // fetches. A token carried as a leading path segment (…/s/<token>/hls/…) is inherited by every
17
+ // relative child URL, so one token authorizes the whole HLS tree.
18
+ //
19
+ // Reuses cursedbelt's ONE jose integration (`signToken`/`verifyPurposeToken`, HS256) — the
20
+ // same primitive the vault/files/pm unlock tokens use — under a dedicated 'media-stream'
21
+ // purpose so it can never be replayed as an access token (or vice-versa).
22
+ const MEDIA_STREAM_PURPOSE = 'media-stream';
23
+ export function createStreamTokenService(opts) {
24
+ const ttl = opts.defaultTtlSeconds ?? 6 * 60 * 60;
25
+ return {
26
+ async sign(grant, ttlSeconds = ttl) {
27
+ // userId carries the owner; src/vid are extra claims (JWTPayload allows them). sessionId
28
+ // is unused for stream tokens (they're not tied to a revocable session row) — a stable
29
+ // marker keeps the required-field contract without implying a session.
30
+ return signToken({
31
+ userId: grant.owner,
32
+ sessionId: 'media-stream',
33
+ roles: [],
34
+ purpose: MEDIA_STREAM_PURPOSE,
35
+ src: grant.sourceId,
36
+ vid: grant.videoId,
37
+ }, opts.secret, ttlSeconds);
38
+ },
39
+ async verify(token) {
40
+ try {
41
+ const p = await verifyPurposeToken(token, opts.secret, MEDIA_STREAM_PURPOSE);
42
+ const src = p.src;
43
+ const vid = p.vid;
44
+ if (typeof src !== 'string' || typeof vid !== 'string')
45
+ return null;
46
+ return { owner: p.userId, sourceId: src, videoId: vid };
47
+ }
48
+ catch {
49
+ // Bad signature / expiry / purpose mismatch — deny.
50
+ return null;
51
+ }
52
+ },
53
+ };
54
+ }
@@ -0,0 +1,65 @@
1
+ export type TranscodeJobStatus = 'pending' | 'processing' | 'ready' | 'failed';
2
+ export type TranscodeJobResult = {
3
+ durationSeconds?: number;
4
+ hasHls?: boolean;
5
+ hasPoster?: boolean;
6
+ hasSprite?: boolean;
7
+ hasThumbnail?: boolean;
8
+ hasDisplay?: boolean;
9
+ };
10
+ export type TranscodeJobStatusBody = {
11
+ status: TranscodeJobStatus;
12
+ progress?: number;
13
+ error?: string;
14
+ result?: TranscodeJobResult;
15
+ };
16
+ export type TranscodeJobClientConfig = {
17
+ /** e.g. 'https://binary-server.cursedalchemy.com' — same value as the byte store's baseUrl. */
18
+ baseUrl: string;
19
+ /** App namespace (path + token scope), e.g. 'notes'. Keys are sent app-scoped. */
20
+ appId: string;
21
+ /** Shared server-to-server secret (x-internal-secret). */
22
+ internalSecret: string;
23
+ /** Poll cadence while a job runs. */
24
+ pollMs?: number;
25
+ /** Give up after this long (the job keeps running server-side; the caller just stops waiting). */
26
+ timeoutMs?: number;
27
+ fetchImpl?: typeof fetch;
28
+ /** Injectable clock/sleep for tests. */
29
+ sleep?: (ms: number) => Promise<void>;
30
+ };
31
+ export type TranscodeJobClient = {
32
+ /** Prefix a bare key with this client's app namespace, as the API expects. */
33
+ appKey: (key: string) => string;
34
+ submit: (body: Record<string, unknown>) => Promise<string>;
35
+ poll: (jobId: string, onProgress?: (pct: number) => void) => Promise<TranscodeJobStatusBody>;
36
+ /**
37
+ * Still-image derivatives: a width-bounded JPEG thumbnail at `thumbKey`, plus — only for
38
+ * formats browsers cannot render (HEIC/HEIF) — a full-size display JPEG at `displayKey`.
39
+ * Keys are bare (this client scopes them).
40
+ */
41
+ imageThumbnail: (opts: {
42
+ sourceKey: string;
43
+ thumbKey: string;
44
+ displayKey?: string;
45
+ /** Job label in binary-server's queue; defaults to the source key. */
46
+ nodeId?: string;
47
+ }) => Promise<{
48
+ hasThumbnail: boolean;
49
+ hasDisplay: boolean;
50
+ }>;
51
+ /**
52
+ * A video's poster frame ONLY — no ladder, no HLS, no sprite sheet. `posterOnly` collapses
53
+ * the library strategy to probe + poster, which is what a grid tile needs and is an order
54
+ * of magnitude cheaper than a transcode nobody asked for.
55
+ */
56
+ videoPoster: (opts: {
57
+ sourceKey: string;
58
+ posterKey: string;
59
+ nodeId?: string;
60
+ }) => Promise<{
61
+ hasPoster: boolean;
62
+ durationSeconds: number;
63
+ }>;
64
+ };
65
+ export declare function createTranscodeJobClient(cfg: TranscodeJobClientConfig): TranscodeJobClient;