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,185 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
3
+ import { subtle } from 'node:crypto';
4
+ import { existsSync } from 'node:fs';
5
+ import { mkdtemp, rm } from 'node:fs/promises';
6
+ import { tmpdir } from 'node:os';
7
+ import path from 'node:path';
8
+ import { pickFreePort, startTestServer, type TestServer } from 'cwip/testing';
9
+ import { runMigrations } from '../migration/runner';
10
+ import { createBinaryServerStore } from './binaryServerStore';
11
+ import {
12
+ type ChunkedUploadService,
13
+ createChunkedUploadService,
14
+ createSqliteFileCatalogue,
15
+ createStorageOpsLedger,
16
+ FILE_CATALOGUE_MIGRATIONS,
17
+ } from './files';
18
+ import type { MediaCtx } from './types';
19
+
20
+ // End-to-end PROOF of the large-file (chunked) upload against a REAL binary-server: spawn bs with
21
+ // a boot-registered app (our RS256 public key), then run the exact client flow — init → PUT each
22
+ // chunk DIRECT to bs → bs assembles on the last chunk → complete registers the catalogue row over
23
+ // the assembled bytes — and read the bytes back through the store. Skips cleanly when the local
24
+ // binary-server checkout isn't present (CI / other machines).
25
+ const BS_DIR = process.env.BINARY_SERVER_DIR ?? '';
26
+ const BS_PRESENT = BS_DIR !== '' && existsSync(path.join(BS_DIR, 'src', 'index.ts'));
27
+ const ISSUER = 'https://demo.local';
28
+ const APP_ID = 'demo';
29
+ const CTX: MediaCtx = { userId: 'user-stub', tenantId: 'user-stub' };
30
+
31
+ const b64url = (b: ArrayBuffer | Uint8Array) =>
32
+ Buffer.from(b instanceof Uint8Array ? b : new Uint8Array(b)).toString('base64url');
33
+ const jsonSeg = (o: unknown) => b64url(new TextEncoder().encode(JSON.stringify(o)));
34
+ const pem = (der: ArrayBuffer, label: string) =>
35
+ `-----BEGIN ${label}-----\n${(
36
+ Buffer.from(der)
37
+ .toString('base64')
38
+ .match(/.{1,64}/g) ?? []
39
+ ).join('\n')}\n-----END ${label}-----\n`;
40
+
41
+ // SKIPPED until a real binary-server checkout is present: point BINARY_SERVER_DIR at one.
42
+ // There is deliberately no default — a published package must not go looking in one machine's
43
+ // directory layout — and `binary-server` has not been re-homed here, so this skips on every host today.
44
+ describe.skipIf(!BS_PRESENT)('chunked upload → real binary-server (integration)', () => {
45
+ let server: TestServer | null = null;
46
+ let tmp = '';
47
+ let base = '';
48
+ let svc: ChunkedUploadService;
49
+ let store: ReturnType<typeof createBinaryServerStore>;
50
+ let signJwt: (claims: Record<string, unknown>) => Promise<string>;
51
+
52
+ beforeAll(async () => {
53
+ // RS256 keypair — bs verifies our tokens with the exported public key; we sign with the private.
54
+ const kp = await subtle.generateKey(
55
+ {
56
+ name: 'RSASSA-PKCS1-v1_5',
57
+ modulusLength: 2048,
58
+ publicExponent: new Uint8Array([1, 0, 1]),
59
+ hash: 'SHA-256',
60
+ },
61
+ true,
62
+ ['sign', 'verify'],
63
+ );
64
+ const spki = pem(await subtle.exportKey('spki', kp.publicKey), 'PUBLIC KEY');
65
+ signJwt = async (claims) => {
66
+ const now = Math.floor(Date.now() / 1000);
67
+ const input = `${jsonSeg({ alg: 'RS256', typ: 'JWT' })}.${jsonSeg({ iss: ISSUER, iat: now, exp: now + 3600, ...claims })}`;
68
+ const sig = await subtle.sign(
69
+ 'RSASSA-PKCS1-v1_5',
70
+ kp.privateKey,
71
+ new TextEncoder().encode(input),
72
+ );
73
+ return `${input}.${b64url(new Uint8Array(sig))}`;
74
+ };
75
+
76
+ tmp = await mkdtemp(path.join(tmpdir(), 'bs-int-'));
77
+ const port = await pickFreePort();
78
+ server = await startTestServer({
79
+ cwd: BS_DIR,
80
+ cmd: ['bun', path.join(BS_DIR, 'src', 'index.ts')],
81
+ port,
82
+ env: {
83
+ PORT: String(port),
84
+ HOST: '127.0.0.1',
85
+ STORAGE_ROOT: path.join(tmp, 'binaries'),
86
+ DB_PATH: path.join(tmp, 'bs.sqlite'),
87
+ FILE_TOKEN_PUBLIC_KEY: spki,
88
+ FILE_TOKEN_ISSUER: ISSUER,
89
+ CA_APP_ID: APP_ID,
90
+ INTERNAL_SECRET: 'demo-internal',
91
+ },
92
+ healthPath: '/healthz',
93
+ timeoutMs: 90_000,
94
+ });
95
+ base = server.baseUrl;
96
+
97
+ const signFileToken = (c: {
98
+ k?: string;
99
+ p?: string;
100
+ fn?: string;
101
+ dl?: 'inline' | 'attachment';
102
+ }) => signJwt(c);
103
+ store = createBinaryServerStore({
104
+ baseUrl: base,
105
+ appId: APP_ID,
106
+ signFileToken,
107
+ internalSecret: 'demo-internal',
108
+ });
109
+ const db = new Database(':memory:');
110
+ runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
111
+ const catalogue = createSqliteFileCatalogue({
112
+ db,
113
+ store,
114
+ workDir: path.join(tmp, 'work'),
115
+ owner: (c) => c.userId,
116
+ });
117
+ const ledger = createStorageOpsLedger(db);
118
+ svc = createChunkedUploadService({
119
+ catalogue,
120
+ ledger,
121
+ backend: 'binary',
122
+ chunkSize: 64 * 1024, // small so a modest test file splits into several chunks
123
+ owner: (c) => c.userId,
124
+ mintChunkUpload: async ({ key, sid, ci, tc, owner }) => {
125
+ const token = await signJwt({
126
+ k: `${APP_ID}/${key}`,
127
+ uid: owner,
128
+ sid,
129
+ ci,
130
+ tc,
131
+ });
132
+ return `${base}/upload-chunk/${APP_ID}/${key}?token=${encodeURIComponent(token)}`;
133
+ },
134
+ });
135
+ }, 90_000);
136
+
137
+ afterAll(async () => {
138
+ await server?.stop();
139
+ if (tmp) await rm(tmp, { recursive: true, force: true });
140
+ });
141
+
142
+ test('uploads a multi-chunk file direct to bs, registers it, and reads the bytes back intact', async () => {
143
+ // A ~200 KiB payload → 4 chunks at 64 KiB. Deterministic bytes so we can verify integrity.
144
+ const size = 200 * 1024;
145
+ const payload = new Uint8Array(size);
146
+ for (let i = 0; i < size; i++) payload[i] = (i * 31 + 7) & 0xff;
147
+
148
+ const init = await svc.init(CTX, {
149
+ name: 'big.bin',
150
+ mime: 'application/octet-stream',
151
+ size,
152
+ tags: ['large'],
153
+ });
154
+ expect(init.chunkCount).toBe(Math.ceil(size / (64 * 1024)));
155
+ expect(init.chunkUploadUrls).toHaveLength(init.chunkCount);
156
+
157
+ // PUT each chunk DIRECT to bs (as the browser would). The last returns 201 assembled.
158
+ let assembled = false;
159
+ for (let ci = 0; ci < init.chunkCount; ci++) {
160
+ const start = ci * init.chunkSize;
161
+ const slice = payload.slice(start, Math.min(start + init.chunkSize, size));
162
+ const res = await fetch(init.chunkUploadUrls[ci], {
163
+ method: 'PUT',
164
+ headers: { 'content-type': 'application/octet-stream' },
165
+ body: slice,
166
+ });
167
+ expect(res.ok).toBe(true);
168
+ if (res.status === 201)
169
+ assembled = ((await res.json()) as { assembled?: boolean }).assembled === true;
170
+ }
171
+ expect(assembled).toBe(true); // bs assembled on the final chunk
172
+
173
+ // Complete → the catalogue row exists over the already-assembled bytes (no re-upload).
174
+ const rec = await svc.complete(init.sid, { size });
175
+ expect(rec?.id).toBe(init.fileId);
176
+ expect(rec?.name).toBe('big.bin');
177
+ expect(rec?.tags).toEqual(['large']);
178
+
179
+ // Read the assembled bytes back THROUGH the store (bs Range GET) — byte-for-byte intact.
180
+ const back = await store.get(`fs/${CTX.userId}/${init.fileId}`);
181
+ expect(back).not.toBeNull();
182
+ expect(back?.length).toBe(size);
183
+ expect(Buffer.from(back as Uint8Array).equals(Buffer.from(payload))).toBe(true);
184
+ }, 30_000);
185
+ });
@@ -0,0 +1,83 @@
1
+ import { mkdir, rm, unlink } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import type { MediaStore } from './types';
4
+
5
+ // Local-disk MediaStore (the dev default). Bun-native: Bun.file/Bun.write handle web streams
6
+ // and byte-range slices directly, so there is no node:stream on the hot path. Keys are
7
+ // generated server-side (owner/id…) but we still guard against path traversal so a stored key
8
+ // can never escape the media root.
9
+ export function createDiskStore(root: string): MediaStore {
10
+ const resolveKey = (key: string): string => {
11
+ const full = path.join(root, key);
12
+ const rel = path.relative(root, full);
13
+ if (rel.startsWith('..') || path.isAbsolute(rel)) {
14
+ throw new Error(`Invalid media key: ${key}`);
15
+ }
16
+ return full;
17
+ };
18
+
19
+ return {
20
+ async put(key, bytes) {
21
+ // Bun.write creates intermediate directories automatically.
22
+ await Bun.write(resolveKey(key), bytes);
23
+ },
24
+ async putStream(key, body) {
25
+ const full = resolveKey(key);
26
+ // Grab the reader BEFORE any real await: in Bun 1.3.x a request-body stream's
27
+ // internals detach across an I/O tick, after which releaseLock() (and the
28
+ // async iterator) throw "undefined is not a function" — 500ing every upload.
29
+ const reader = body.getReader();
30
+ await mkdir(path.dirname(full), { recursive: true });
31
+ // Stream through a Bun FileSink — peak memory is one chunk. (NB: `Bun.write(full,
32
+ // new Response(body))` HANGS on a web ReadableStream in Bun 1.3.x, so we pump manually.)
33
+ const writer = Bun.file(full).writer();
34
+ try {
35
+ while (true) {
36
+ const { done, value } = await reader.read();
37
+ if (done) break;
38
+ writer.write(value);
39
+ }
40
+ await writer.end();
41
+ } finally {
42
+ try {
43
+ reader.releaseLock();
44
+ } catch {
45
+ // Same Bun 1.3.x detach bug via a caller-side await (e.g. an async auth
46
+ // guard): reads still complete, only this cleanup call explodes — and by
47
+ // here the stream is fully consumed, so there is nothing left to release.
48
+ }
49
+ }
50
+ },
51
+ async get(key) {
52
+ const file = Bun.file(resolveKey(key));
53
+ if (!(await file.exists())) return null;
54
+ return new Uint8Array(await file.arrayBuffer());
55
+ },
56
+ async remove(key) {
57
+ try {
58
+ await unlink(resolveKey(key));
59
+ } catch (err) {
60
+ if ((err as NodeJS.ErrnoException)?.code !== 'ENOENT') throw err;
61
+ }
62
+ },
63
+ async removePrefix(prefix) {
64
+ await rm(resolveKey(prefix), { recursive: true, force: true });
65
+ },
66
+ async stat(key) {
67
+ const file = Bun.file(resolveKey(key));
68
+ if (!(await file.exists())) return null;
69
+ return { size: file.size };
70
+ },
71
+ async getRange(key, range) {
72
+ const file = Bun.file(resolveKey(key));
73
+ if (!(await file.exists())) return null;
74
+ // Bun.file.slice(start, end) is exclusive-end; our ranges are inclusive.
75
+ const blob = range ? file.slice(range.start, range.end + 1) : file;
76
+ return { stream: blob.stream() };
77
+ },
78
+ async localPath(key) {
79
+ const full = resolveKey(key);
80
+ return (await Bun.file(full).exists()) ? full : null;
81
+ },
82
+ };
83
+ }
@@ -0,0 +1,169 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
3
+ import { mkdtemp, rm } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { isFfmpegAvailable } from '../../media-bun';
7
+ import { runMigrations } from '../../migration/runner';
8
+ import { createDiskStore } from '../diskStore';
9
+ import type { MediaUrls } from '../sources';
10
+ import { fsDisplayKey, type MediaCtx } from '../types';
11
+ import { createCataloguePhotoSource } from './adapter';
12
+ import { createSqliteFileCatalogue } from './catalogue';
13
+ import type { FileUrls } from './fileRoutes';
14
+ import { FILE_CATALOGUE_MIGRATIONS } from './migrations';
15
+ import type { FileCatalogue, PrivateFileCatalogue } from './types';
16
+ import { bytesStream } from 'cursedbelt-core/testing';
17
+
18
+ // End-to-end: an HEIC photo must gain a browser-safe full-size display derivative + advertise it as
19
+ // displayUrl; a normal JPEG must NOT (its raw url is already renderable).
20
+
21
+ const haveFfmpeg = await isFfmpegAvailable();
22
+ const CTX: MediaCtx = { userId: 'u1', tenantId: 't1' };
23
+
24
+ const fakeFileUrls: FileUrls = {
25
+ async build(_ctx, fileId, mime) {
26
+ return { url: `/_files/s/tok/${fileId}?ct=${encodeURIComponent(mime)}` };
27
+ },
28
+ };
29
+ const fakeMediaUrls: MediaUrls = {
30
+ async build(_ctx, sourceId, videoId) {
31
+ const p = `/_media/s/tok/${sourceId}/${videoId}`;
32
+ return {
33
+ streamUrl: `${p}/media`,
34
+ hlsUrl: `${p}/hls`,
35
+ posterUrl: `${p}/poster`,
36
+ displayUrl: `${p}/display`,
37
+ vttUrl: `${p}/vtt`,
38
+ spriteUrl: `${p}/sprite`,
39
+ };
40
+ },
41
+ };
42
+
43
+ /** A real HEVC-in-HEIF still, the format sharp can't decode but ffmpeg can. */
44
+ async function heicBytes(): Promise<Uint8Array | null> {
45
+ const dir = await mkdtemp(path.join(tmpdir(), 'fc-heic-src-'));
46
+ const dest = path.join(dir, 'src.heic');
47
+ const proc = Bun.spawn(
48
+ [
49
+ 'ffmpeg',
50
+ '-hide_banner',
51
+ '-y',
52
+ '-f',
53
+ 'lavfi',
54
+ '-i',
55
+ 'testsrc=duration=1:size=800x600:rate=1',
56
+ '-frames:v',
57
+ '1',
58
+ '-c:v',
59
+ 'libx265',
60
+ '-tag:v',
61
+ 'hvc1',
62
+ '-f',
63
+ 'hevc',
64
+ dest,
65
+ ],
66
+ { stdout: 'ignore', stderr: 'ignore' },
67
+ );
68
+ await proc.exited;
69
+ if (proc.exitCode !== 0) {
70
+ await rm(dir, { recursive: true, force: true });
71
+ return null; // libx265 not built in — caller skips
72
+ }
73
+ const bytes = new Uint8Array(await Bun.file(dest).arrayBuffer());
74
+ await rm(dir, { recursive: true, force: true });
75
+ return bytes;
76
+ }
77
+
78
+ // SKIPPED on a host without ffmpeg/ffprobe — it synthesizes and decodes real media. Runs by default wherever ffmpeg is installed.
79
+ describe.skipIf(!haveFfmpeg)('photo display derivative (HEIC)', () => {
80
+ let db: Database;
81
+ let root: string;
82
+ let cat: FileCatalogue & PrivateFileCatalogue;
83
+
84
+ beforeAll(async () => {
85
+ db = new Database(':memory:');
86
+ runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
87
+ root = await mkdtemp(path.join(tmpdir(), 'fc-photo-display-'));
88
+ cat = createSqliteFileCatalogue({
89
+ db,
90
+ store: createDiskStore(root),
91
+ workDir: path.join(root, 'w'),
92
+ owner: (c) => c.userId,
93
+ });
94
+ });
95
+ afterAll(async () => {
96
+ db.close();
97
+ await rm(root, { recursive: true, force: true });
98
+ });
99
+
100
+ const source = () =>
101
+ createCataloguePhotoSource({
102
+ id: 'photos',
103
+ title: 'Photos',
104
+ catalogue: cat,
105
+ fileUrls: fakeFileUrls,
106
+ mediaUrls: fakeMediaUrls,
107
+ });
108
+
109
+ const settle = async (id: string) => {
110
+ for (let i = 0; i < 60; i++) {
111
+ if ((await cat.get(CTX, id))?.hasThumbnail) break;
112
+ await new Promise((r) => setTimeout(r, 50));
113
+ }
114
+ };
115
+
116
+ test('HEIC → display derivative stored + displayUrl advertised', async () => {
117
+ const bytes = await heicBytes();
118
+ if (!bytes) return; // no libx265 in this ffmpeg
119
+ const rec = await cat.ingest(CTX, {
120
+ name: 'photo.heic',
121
+ mime: 'image/heic',
122
+ body: bytesStream(bytes),
123
+ });
124
+ await settle(rec.id);
125
+
126
+ const store = createDiskStore(root);
127
+ expect(await store.stat(fsDisplayKey(CTX.userId, rec.id))).not.toBeNull();
128
+
129
+ const photo = await source().resolve(CTX, rec.id);
130
+ expect(photo?.thumbnailUrl).toBeDefined();
131
+ expect(photo?.displayUrl).toBe(`/_media/s/tok/photos/${rec.id}/display`);
132
+ });
133
+
134
+ test('JPEG → no display derivative, no displayUrl (raw is renderable)', async () => {
135
+ // A real JPEG sharp can resize.
136
+ const dir = await mkdtemp(path.join(tmpdir(), 'fc-jpg-'));
137
+ const jpgPath = path.join(dir, 'x.jpg');
138
+ const proc = Bun.spawn(
139
+ [
140
+ 'ffmpeg',
141
+ '-hide_banner',
142
+ '-y',
143
+ '-f',
144
+ 'lavfi',
145
+ '-i',
146
+ 'testsrc=size=320x240:rate=1',
147
+ '-frames:v',
148
+ '1',
149
+ jpgPath,
150
+ ],
151
+ { stdout: 'ignore', stderr: 'ignore' },
152
+ );
153
+ await proc.exited;
154
+ const bytes = new Uint8Array(await Bun.file(jpgPath).arrayBuffer());
155
+ await rm(dir, { recursive: true, force: true });
156
+
157
+ const rec = await cat.ingest(CTX, {
158
+ name: 'photo.jpg',
159
+ mime: 'image/jpeg',
160
+ body: new Response(bytes).body as ReadableStream<Uint8Array>,
161
+ });
162
+ await settle(rec.id);
163
+
164
+ const store = createDiskStore(root);
165
+ expect(await store.stat(fsDisplayKey(CTX.userId, rec.id))).toBeNull();
166
+ const photo = await source().resolve(CTX, rec.id);
167
+ expect(photo?.displayUrl).toBeUndefined();
168
+ });
169
+ });
@@ -0,0 +1,128 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
3
+ import { mkdtemp, rm } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { runMigrations } from '../../migration/runner';
7
+ import { createDiskStore } from '../diskStore';
8
+ import type { MediaUrls } from '../sources';
9
+ import type { MediaCtx } from '../types';
10
+ import { createCataloguePhotoSource } from './adapter';
11
+ import { createSqliteFileCatalogue } from './catalogue';
12
+ import type { FileUrls } from './fileRoutes';
13
+ import { FILE_CATALOGUE_MIGRATIONS } from './migrations';
14
+ import type { FileCatalogue, PrivateFileCatalogue } from './types';
15
+
16
+ // The photo source is an image-filtered VIEW over the catalogue: primary bytes via the file
17
+ // route URL, grid thumb via the type-agnostic poster URL (only when the thumbnailer produced one).
18
+
19
+ const CTX: MediaCtx = { userId: 'u1', tenantId: 't1' };
20
+ const stream = (s: string) =>
21
+ new Response(new TextEncoder().encode(s)).body as ReadableStream<Uint8Array>;
22
+
23
+ const fakeFileUrls: FileUrls = {
24
+ async build(_ctx, fileId, mime) {
25
+ return { url: `/_files/s/tok/${fileId}?ct=${encodeURIComponent(mime)}` };
26
+ },
27
+ };
28
+ const fakeMediaUrls: MediaUrls = {
29
+ async build(_ctx, sourceId, videoId) {
30
+ const p = `/_media/s/tok/${sourceId}/${videoId}`;
31
+ return {
32
+ streamUrl: `${p}/media`,
33
+ hlsUrl: `${p}/hls`,
34
+ posterUrl: `${p}/poster`,
35
+ displayUrl: `${p}/display`,
36
+ vttUrl: `${p}/vtt`,
37
+ spriteUrl: `${p}/sprite`,
38
+ };
39
+ },
40
+ };
41
+
42
+ describe('createCataloguePhotoSource', () => {
43
+ let db: Database;
44
+ let root: string;
45
+ let cat: FileCatalogue & PrivateFileCatalogue;
46
+ let photoId: string;
47
+ let videoId: string;
48
+
49
+ beforeAll(async () => {
50
+ db = new Database(':memory:');
51
+ runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
52
+ root = await mkdtemp(path.join(tmpdir(), 'fc-photo-'));
53
+ let clock = 1;
54
+ cat = createSqliteFileCatalogue({
55
+ db,
56
+ store: createDiskStore(root),
57
+ workDir: path.join(root, 'w'),
58
+ owner: (c) => c.userId,
59
+ now: () => ++clock,
60
+ });
61
+ });
62
+ afterAll(async () => {
63
+ // The fake video's fire-and-forget transcode fails asynchronously — let it settle before
64
+ // closing the db, or its progress write hits a finalized statement between test files.
65
+ for (let i = 0; i < 40; i++) {
66
+ const status = (await cat.get(CTX, videoId))?.processingStatus;
67
+ if (status && status !== 'processing' && status !== 'pending') break;
68
+ await new Promise((r) => setTimeout(r, 50));
69
+ }
70
+ db.close();
71
+ await rm(root, { recursive: true, force: true });
72
+ });
73
+
74
+ const source = () =>
75
+ createCataloguePhotoSource({
76
+ id: 'photos',
77
+ title: 'Photos',
78
+ catalogue: cat,
79
+ fileUrls: fakeFileUrls,
80
+ mediaUrls: fakeMediaUrls,
81
+ });
82
+
83
+ test('lists ONLY images — video/doc rows are invisible to the photo view', async () => {
84
+ const img = await cat.ingest(CTX, {
85
+ name: 'a.jpg',
86
+ // a fake-jpeg text body: fine — the thumbnailer fails quietly and has_thumbnail stays 0
87
+ mime: 'image/jpeg',
88
+ body: stream('img-bytes'),
89
+ tags: ['trip'],
90
+ });
91
+ photoId = img.id;
92
+ videoId = (await cat.ingest(CTX, { name: 'b.mp4', mime: 'video/mp4', body: stream('vid') })).id;
93
+ await cat.ingest(CTX, { name: 'c.txt', mime: 'text/plain', body: stream('txt') });
94
+
95
+ const page = await source().list(CTX, {});
96
+ expect(page.items.map((p) => p.name)).toEqual(['a.jpg']);
97
+ const photo = page.items[0];
98
+ expect(photo.type).toBe('image');
99
+ expect(photo.url).toBe(`/_files/s/tok/${photoId}?ct=image%2Fjpeg`);
100
+ expect(photo.thumbnailUrl).toBeUndefined(); // no thumbnail produced for fake bytes
101
+ expect(photo.tags).toEqual(['trip']);
102
+ });
103
+
104
+ test('resolve: image → GalleryPhoto; non-image id → null', async () => {
105
+ expect((await source().resolve(CTX, photoId))?.name).toBe('a.jpg');
106
+ const all = await cat.list(CTX, {});
107
+ const video = all.items.find((f) => f.fileType === 'video');
108
+ expect(await source().resolve(CTX, video?.id ?? '')).toBeNull();
109
+ });
110
+
111
+ test('ingest rejects non-image uploads', async () => {
112
+ expect(
113
+ source().ingest?.(CTX, { name: 'x.mp4', mime: 'video/mp4', body: stream('v') }),
114
+ ).rejects.toThrow(/image/);
115
+ });
116
+
117
+ test('remove trashes (soft-deletes) through to the catalogue, not a hard delete', async () => {
118
+ const rec = await cat.ingest(CTX, {
119
+ name: 'gone.png',
120
+ mime: 'image/png',
121
+ body: stream('bye'),
122
+ });
123
+ await source().remove?.(CTX, rec.id);
124
+ // Trashed, not gone — the row + bytes survive until an explicit hard-delete (catalogue.remove).
125
+ expect((await cat.get(CTX, rec.id))?.trashedAt).toBeDefined();
126
+ expect((await source().list(CTX, {})).items.some((p) => p.id === rec.id)).toBe(false);
127
+ });
128
+ });
@@ -0,0 +1,121 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
3
+ import { mkdtemp, rm } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { runMigrations } from '../../migration/runner';
7
+ import { createDiskStore } from '../diskStore';
8
+ import type { MediaUrls } from '../sources';
9
+ import type { MediaCtx } from '../types';
10
+ import { createCatalogueVideoSource, createFileToPlayable } from './adapter';
11
+ import { createSqliteFileCatalogue } from './catalogue';
12
+ import { FILE_CATALOGUE_MIGRATIONS } from './migrations';
13
+ import type { FileCatalogue, FileRecord } from './types';
14
+
15
+ const CTX: MediaCtx = { userId: 'u1', tenantId: 't1' };
16
+ const stream = (s: string) =>
17
+ new Response(new TextEncoder().encode(s)).body as ReadableStream<Uint8Array>;
18
+
19
+ // A fake URL builder — the real one signs a token; here we just echo the ids so the adapter's
20
+ // URL wiring is testable without a signing service.
21
+ const fakeUrls: MediaUrls = {
22
+ async build(_ctx, sourceId, videoId) {
23
+ const p = `/_media/s/tok/${sourceId}/${videoId}`;
24
+ return {
25
+ streamUrl: `${p}/media`,
26
+ hlsUrl: `${p}/hls`,
27
+ posterUrl: `${p}/poster`,
28
+ displayUrl: `${p}/display`,
29
+ vttUrl: `${p}/vtt`,
30
+ spriteUrl: `${p}/sprite`,
31
+ };
32
+ },
33
+ };
34
+
35
+ const videoRecord = (over: Partial<FileRecord> = {}): FileRecord => ({
36
+ id: 'v1',
37
+ name: 'clip.mp4',
38
+ mime: 'video/mp4',
39
+ size: 10,
40
+ fileType: 'video',
41
+ tags: ['fun'],
42
+ isPrivate: false,
43
+ favorite: false,
44
+ collectionIds: [],
45
+ hasThumbnail: true,
46
+ hasHls: true,
47
+ hasSprites: false,
48
+ durationSeconds: 12,
49
+ processingStatus: 'ready',
50
+ processingProgress: 100,
51
+ createdAt: 1,
52
+ updatedAt: 1,
53
+ ...over,
54
+ });
55
+
56
+ describe('createFileToPlayable', () => {
57
+ const toPlayable = createFileToPlayable(fakeUrls, 'files');
58
+
59
+ test('maps a ready video, gating optional URLs on capability flags', async () => {
60
+ const p = await toPlayable(CTX, videoRecord());
61
+ expect(p).not.toBeNull();
62
+ expect(p?.streamUrl).toBe('/_media/s/tok/files/v1/media');
63
+ expect(p?.hlsUrl).toBe('/_media/s/tok/files/v1/hls'); // hasHls
64
+ expect(p?.posterUrl).toBe('/_media/s/tok/files/v1/poster'); // hasThumbnail
65
+ expect(p?.vttUrl).toBeUndefined(); // hasSprites false
66
+ expect(p?.spriteUrl).toBeUndefined();
67
+ expect(p?.tags).toEqual(['fun']);
68
+ expect(p?.processingStatus).toBe('ready');
69
+ });
70
+
71
+ test('returns null for a non-video file', async () => {
72
+ expect(await toPlayable(CTX, videoRecord({ fileType: 'image', mime: 'image/png' }))).toBeNull();
73
+ });
74
+ });
75
+
76
+ describe('createCatalogueVideoSource (gallery = view over catalogue)', () => {
77
+ let db: Database;
78
+ let root: string;
79
+ let cat: FileCatalogue;
80
+
81
+ beforeAll(async () => {
82
+ db = new Database(':memory:');
83
+ runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
84
+ root = await mkdtemp(path.join(tmpdir(), 'fc-adapter-'));
85
+ let clock = 1;
86
+ cat = createSqliteFileCatalogue({
87
+ db,
88
+ store: createDiskStore(root),
89
+ workDir: path.join(root, 'w'),
90
+ now: () => ++clock,
91
+ });
92
+ // Mix media types; only videos should surface through the gallery source.
93
+ await cat.ingest(CTX, { name: 'a.png', mime: 'image/png', body: stream('a'), tags: ['pic'] });
94
+ await cat.ingest(CTX, { name: 'doc.pdf', mime: 'application/pdf', body: stream('d') });
95
+ });
96
+ afterAll(async () => {
97
+ db.close();
98
+ await rm(root, { recursive: true, force: true });
99
+ });
100
+
101
+ test('lists only videos (none here) — images/docs are filtered out', async () => {
102
+ const src = createCatalogueVideoSource({
103
+ id: 'files',
104
+ title: 'Files',
105
+ catalogue: cat,
106
+ urls: fakeUrls,
107
+ });
108
+ const page = await src.list(CTX, {});
109
+ expect(page.items).toEqual([]); // catalogue has an image + a doc, zero videos
110
+ });
111
+
112
+ test('tags() reflects the catalogue facets', async () => {
113
+ const src = createCatalogueVideoSource({
114
+ id: 'files',
115
+ title: 'Files',
116
+ catalogue: cat,
117
+ urls: fakeUrls,
118
+ });
119
+ expect(await src.tags?.(CTX)).toContain('pic');
120
+ });
121
+ });