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,114 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
3
+ import { existsSync, mkdtempSync, readdirSync, rmSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { createSqliteBackupService, snapshotSqlite } from './backup';
7
+
8
+ describe('createSqliteBackupService', () => {
9
+ let db: Database;
10
+ let dir: string;
11
+
12
+ beforeEach(() => {
13
+ dir = mkdtempSync(join(tmpdir(), 'cb-backup-'));
14
+ db = new Database(':memory:');
15
+ db.run('CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)');
16
+ for (let i = 0; i < 50; i++) db.run('INSERT INTO t (v) VALUES (?)', [`row-${i}`]);
17
+ });
18
+
19
+ afterEach(() => {
20
+ db.close();
21
+ rmSync(dir, { recursive: true, force: true });
22
+ });
23
+
24
+ test('runNow writes a real, queryable backup and reports its size', async () => {
25
+ const svc = createSqliteBackupService(db, { backupDir: dir });
26
+ const { path, sizeMb } = await svc.runNow();
27
+
28
+ expect(existsSync(path)).toBe(true);
29
+ expect(sizeMb).toBeGreaterThan(0);
30
+
31
+ // The backup is a valid SQLite DB with the source data.
32
+ const copy = new Database(path, { readonly: true });
33
+ expect((copy.query('SELECT COUNT(*) AS n FROM t').get() as { n: number }).n).toBe(50);
34
+ copy.close();
35
+ });
36
+
37
+ test('listBackups reports newest first', async () => {
38
+ const svc = createSqliteBackupService(db, { backupDir: dir });
39
+ await svc.runNow();
40
+ await svc.runNow();
41
+ const backups = svc.listBackups();
42
+ expect(backups).toHaveLength(2);
43
+ expect(backups[0].createdAt.getTime()).toBeGreaterThanOrEqual(backups[1].createdAt.getTime());
44
+ for (const b of backups) expect(b.sizeMb).toBeGreaterThan(0);
45
+ });
46
+
47
+ test('prunes to maxBackups after each run', async () => {
48
+ const svc = createSqliteBackupService(db, { backupDir: dir, maxBackups: 2 });
49
+ await svc.runNow();
50
+ await svc.runNow();
51
+ await svc.runNow();
52
+
53
+ expect(svc.listBackups()).toHaveLength(2);
54
+ // Only the backup files remain on disk.
55
+ expect(readdirSync(dir).filter((f) => f.endsWith('.sqlite'))).toHaveLength(2);
56
+ });
57
+
58
+ test('creates the backup directory if it does not exist', async () => {
59
+ const nested = join(dir, 'a', 'b', 'backups');
60
+ const svc = createSqliteBackupService(db, { backupDir: nested });
61
+ const { path } = await svc.runNow();
62
+ expect(existsSync(path)).toBe(true);
63
+ });
64
+
65
+ test('listBackups returns [] for a missing directory', () => {
66
+ const svc = createSqliteBackupService(db, { backupDir: join(dir, 'nope') });
67
+ expect(svc.listBackups()).toEqual([]);
68
+ });
69
+
70
+ test('pruneOld is a no-op below the threshold', async () => {
71
+ const svc = createSqliteBackupService(db, { backupDir: dir, maxBackups: 5 });
72
+ await svc.runNow();
73
+ svc.pruneOld();
74
+ expect(svc.listBackups()).toHaveLength(1);
75
+ });
76
+ });
77
+
78
+ describe('snapshotSqlite', () => {
79
+ let dir: string;
80
+ let src: string;
81
+
82
+ beforeEach(() => {
83
+ dir = mkdtempSync(join(tmpdir(), 'cb-snapshot-'));
84
+ src = join(dir, 'source.sqlite');
85
+ const db = new Database(src);
86
+ db.run('CREATE TABLE t (id INTEGER PRIMARY KEY, v TEXT)');
87
+ for (let i = 0; i < 10; i++) db.run('INSERT INTO t (v) VALUES (?)', [`row-${i}`]);
88
+ db.close();
89
+ });
90
+
91
+ afterEach(() => rmSync(dir, { recursive: true, force: true }));
92
+
93
+ test('writes a consistent, queryable copy and returns its byte size', () => {
94
+ const dest = join(dir, 'snap.sqlite');
95
+ const bytes = snapshotSqlite(src, dest);
96
+ expect(bytes).toBeGreaterThan(0);
97
+
98
+ const copy = new Database(dest, { readonly: true });
99
+ expect((copy.query('SELECT COUNT(*) AS n FROM t').get() as { n: number }).n).toBe(10);
100
+ copy.close();
101
+ });
102
+
103
+ test('refuses an existing destination rather than half-overwriting it', () => {
104
+ const dest = join(dir, 'snap.sqlite');
105
+ snapshotSqlite(src, dest);
106
+ expect(() => snapshotSqlite(src, dest)).toThrow();
107
+ });
108
+
109
+ test('escapes single quotes in the destination path', () => {
110
+ const dest = join(dir, "o'brien.sqlite");
111
+ expect(snapshotSqlite(src, dest)).toBeGreaterThan(0);
112
+ expect(existsSync(dest)).toBe(true);
113
+ });
114
+ });
@@ -0,0 +1,154 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { randomBytes } from 'node:crypto';
3
+ import { existsSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+
6
+ /**
7
+ * SQLite backup service for `bun:sqlite` — the from-scratch replacement for the apps'
8
+ * `backupService.ts`. It uses `VACUUM INTO`, the correct primitive for `bun:sqlite`:
9
+ * it writes a compact, defragmented copy with all WAL data merged in (the Node
10
+ * `better-sqlite3` online `.backup()` API does not exist here). Reads continue under
11
+ * WAL during the copy; only writes serialize behind it — negligible for normal DB
12
+ * sizes, so the `daily-backup` cron job (task 29) runs it off-peak on the main thread.
13
+ *
14
+ * Optional S3 mirroring uses Bun's native S3 client (credentials from the environment,
15
+ * never stored here). The job *definition* lives in the app; this is the reusable
16
+ * mechanism.
17
+ */
18
+
19
+ const BACKUP_PREFIX = 'backup-';
20
+ const BACKUP_EXT = '.sqlite';
21
+ const BYTES_PER_MB = 1_048_576;
22
+
23
+ export interface SqliteBackupS3Opts {
24
+ bucket: string;
25
+ /** Key prefix within the bucket, e.g. `'ca/backups'`. */
26
+ prefix: string;
27
+ /** AWS region; falls back to Bun's env resolution when omitted. */
28
+ region?: string;
29
+ /** Custom endpoint (e.g. an S3-compatible store like R2/MinIO). */
30
+ endpoint?: string;
31
+ }
32
+
33
+ export interface SqliteBackupOpts {
34
+ /** Directory backups are written to (created if missing). */
35
+ backupDir: string;
36
+ /** How many local backups to retain after each run. Default: 7. */
37
+ maxBackups?: number;
38
+ /** Mirror each backup to S3 after writing it locally. */
39
+ s3?: SqliteBackupS3Opts;
40
+ }
41
+
42
+ export interface BackupInfo {
43
+ path: string;
44
+ createdAt: Date;
45
+ sizeMb: number;
46
+ }
47
+
48
+ export interface SqliteBackupService {
49
+ /** Write a fresh backup now, mirror to S3 (if configured), prune, and describe it. */
50
+ runNow(): Promise<{ path: string; sizeMb: number }>;
51
+ /** Local backups, newest first. */
52
+ listBackups(): BackupInfo[];
53
+ /** Delete the oldest backups beyond `maxBackups`. */
54
+ pruneOld(): void;
55
+ }
56
+
57
+ /**
58
+ * `VACUUM INTO` a source db path to a fresh destination file — the path-level
59
+ * snapshot primitive behind `createSqliteBackupService` and the satellites' own
60
+ * backup scripts. It is transactionally consistent even while the db is being
61
+ * written under WAL (unlike `cp`), and it REFUSES an existing target, so a
62
+ * same-instant re-run errors rather than half-overwriting a previous snapshot.
63
+ * Opens the source read-only, closes it, and returns the snapshot's byte size.
64
+ *
65
+ * Use this when the caller chooses the exact destination path (a dated dir, a
66
+ * `latest.sqlite`, a remote-pull staging file); use {@link createSqliteBackupService}
67
+ * when you want a managed backup DIRECTORY with timestamped names + retention + S3.
68
+ */
69
+ export function snapshotSqlite(sourcePath: string, destPath: string): number {
70
+ const source = new Database(sourcePath, { readonly: true });
71
+ try {
72
+ source.run(`VACUUM INTO '${destPath.replaceAll("'", "''")}'`);
73
+ } finally {
74
+ source.close();
75
+ }
76
+ return statSync(destPath).size;
77
+ }
78
+
79
+ const hasBunS3 = (): boolean => typeof (Bun as { S3Client?: unknown }).S3Client === 'function';
80
+
81
+ async function uploadToS3(localPath: string, s3: SqliteBackupS3Opts): Promise<void> {
82
+ if (!hasBunS3()) {
83
+ console.warn(
84
+ '[sqlite-backup] S3 is configured but Bun.S3Client is unavailable; skipping upload. ' +
85
+ 'Upgrade Bun (≥1.1) or remove the s3 option.',
86
+ );
87
+ return;
88
+ }
89
+ const client = new Bun.S3Client({
90
+ bucket: s3.bucket,
91
+ region: s3.region,
92
+ endpoint: s3.endpoint,
93
+ });
94
+ const key = `${s3.prefix.replace(/\/+$/, '')}/${localPath.split('/').pop()}`;
95
+ await client.file(key).write(Bun.file(localPath));
96
+ }
97
+
98
+ export function createSqliteBackupService(
99
+ db: Database,
100
+ opts: SqliteBackupOpts,
101
+ ): SqliteBackupService {
102
+ const { backupDir, maxBackups = 7, s3 } = opts;
103
+
104
+ if (s3 && !hasBunS3()) {
105
+ console.warn(
106
+ '[sqlite-backup] S3 mirroring requested but Bun.S3Client is unavailable in this runtime; ' +
107
+ 'backups will be written locally only.',
108
+ );
109
+ }
110
+
111
+ const service: SqliteBackupService = {
112
+ async runNow() {
113
+ mkdirSync(backupDir, { recursive: true });
114
+ // ISO timestamp (filesystem-safe) + a short random suffix so rapid successive
115
+ // runs in the same millisecond never collide onto one path.
116
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
117
+ const outPath = join(
118
+ backupDir,
119
+ `${BACKUP_PREFIX}${stamp}-${randomBytes(3).toString('hex')}${BACKUP_EXT}`,
120
+ );
121
+
122
+ // VACUUM INTO checkpoints the WAL and writes a clean, compact copy.
123
+ db.run('VACUUM INTO ?', [outPath]);
124
+ const sizeMb = statSync(outPath).size / BYTES_PER_MB;
125
+
126
+ if (s3) await uploadToS3(outPath, s3);
127
+
128
+ service.pruneOld();
129
+ return { path: outPath, sizeMb };
130
+ },
131
+
132
+ listBackups() {
133
+ if (!existsSync(backupDir)) return [];
134
+ return readdirSync(backupDir)
135
+ .filter((f) => f.startsWith(BACKUP_PREFIX) && f.endsWith(BACKUP_EXT))
136
+ .map((f) => {
137
+ const path = join(backupDir, f);
138
+ const stat = statSync(path);
139
+ return { path, createdAt: stat.mtime, sizeMb: stat.size / BYTES_PER_MB };
140
+ })
141
+ .sort(
142
+ (a, b) => b.createdAt.getTime() - a.createdAt.getTime() || b.path.localeCompare(a.path),
143
+ );
144
+ },
145
+
146
+ pruneOld() {
147
+ for (const old of service.listBackups().slice(maxBackups)) {
148
+ rmSync(old.path, { force: true });
149
+ }
150
+ },
151
+ };
152
+
153
+ return service;
154
+ }
@@ -0,0 +1,70 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { describe, expect, test } from 'bun:test';
3
+ import {
4
+ isKnownTable,
5
+ listSqliteTables,
6
+ NotReadOnlyError,
7
+ quoteIdent,
8
+ readSqliteTable,
9
+ runReadonlySqlite,
10
+ UnknownTableError,
11
+ } from './browse';
12
+
13
+ function seed(): Database {
14
+ const db = new Database(':memory:');
15
+ db.run('CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)');
16
+ db.run('CREATE TABLE contacts (id INTEGER PRIMARY KEY, name TEXT)');
17
+ for (let i = 1; i <= 5; i++)
18
+ db.run('INSERT INTO notes (id, body) VALUES (?, ?)', [i, `note ${i}`]);
19
+ return db;
20
+ }
21
+
22
+ describe('listSqliteTables', () => {
23
+ test('returns user tables with row counts, excluding sqlite_*', () => {
24
+ const db = seed();
25
+ db.run('CREATE TABLE x (id INTEGER PRIMARY KEY AUTOINCREMENT)'); // creates sqlite_sequence
26
+ const items = listSqliteTables(db);
27
+ expect(items.map((t) => t.name).sort()).toEqual(['contacts', 'notes', 'x']);
28
+ expect(items.find((t) => t.name === 'notes')?.count).toBe(5);
29
+ expect(items.some((t) => t.name.startsWith('sqlite_'))).toBe(false);
30
+ });
31
+ });
32
+
33
+ describe('readSqliteTable', () => {
34
+ test('clamps and paginates', () => {
35
+ const page = readSqliteTable(seed(), 'notes', { limit: 2, offset: 1 });
36
+ expect(page.items).toHaveLength(2);
37
+ expect(page).toMatchObject({ total: 5, limit: 2, offset: 1 });
38
+ });
39
+
40
+ test('clamps an over-large limit to 1000 and negatives to defaults', () => {
41
+ expect(readSqliteTable(seed(), 'notes', { limit: 99999 }).limit).toBe(1000);
42
+ expect(readSqliteTable(seed(), 'notes', { offset: -4 }).offset).toBe(0);
43
+ });
44
+
45
+ test('throws UnknownTableError for a non-table (injection-safe)', () => {
46
+ const db = seed();
47
+ expect(() => readSqliteTable(db, 'notes"; DROP TABLE notes;--')).toThrow(UnknownTableError);
48
+ expect(isKnownTable(db, 'notes')).toBe(true);
49
+ });
50
+ });
51
+
52
+ describe('runReadonlySqlite', () => {
53
+ test('runs a bound SELECT', () => {
54
+ expect(runReadonlySqlite(seed(), 'SELECT body FROM notes WHERE id = ?', [3])).toEqual([
55
+ { body: 'note 3' },
56
+ ]);
57
+ });
58
+
59
+ test('rejects non-SELECT with NotReadOnlyError (nothing runs)', () => {
60
+ const db = seed();
61
+ expect(() => runReadonlySqlite(db, 'DELETE FROM notes')).toThrow(NotReadOnlyError);
62
+ expect(db.query('SELECT COUNT(*) AS n FROM notes').get()).toEqual({ n: 5 });
63
+ });
64
+ });
65
+
66
+ describe('quoteIdent', () => {
67
+ test('double-quotes and escapes embedded quotes', () => {
68
+ expect(quoteIdent('a"b')).toBe('"a""b"');
69
+ });
70
+ });
@@ -0,0 +1,120 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import { clampPageLimit } from 'cwip/math';
3
+ import { isReadOnlySql } from './readonlySql';
4
+
5
+ /**
6
+ * Hono-free read-only SQLite browse primitives — the shared core beneath
7
+ * `createSqliteViewerMiddleware` (viewer.ts) AND any consumer that can't mount a
8
+ * Hono sub-app (e.g. a cursedshell plugin's `defineService`, whose routes are pure
9
+ * data, or an app with a bespoke transport). One primitive per concern: the
10
+ * middleware is now a thin HTTP wrapper over these functions.
11
+ *
12
+ * Every path is read-only and injection-safe: table names are validated against
13
+ * `sqlite_master` before interpolation, all values are bound, and ad-hoc SQL is
14
+ * gated by {@link isReadOnlySql} (SELECT/WITH only). Authn/authz is the caller's job.
15
+ */
16
+
17
+ /** A sane admin page-size window. */
18
+ const MAX_PAGE = 1000;
19
+
20
+ /** Clamp a requested page size into `[1, MAX_PAGE]`, falling back when unparseable. */
21
+ export function clampLimit(raw: number | string | undefined, fallback: number): number {
22
+ return clampPageLimit(raw, { max: MAX_PAGE, fallback });
23
+ }
24
+
25
+ /** Clamp an offset to a non-negative integer (0 when unparseable). */
26
+ export function clampOffset(raw: number | string | undefined): number {
27
+ return clampPageLimit(raw, { min: 0, max: Number.POSITIVE_INFINITY, fallback: 0 });
28
+ }
29
+
30
+ /** Is `name` an actual table in this DB? (The injection guard for `SELECT * FROM`.) */
31
+ export function isKnownTable(db: Database, name: string): boolean {
32
+ const row = db
33
+ .query<{ n: number }, [string]>(
34
+ "SELECT COUNT(*) AS n FROM sqlite_master WHERE type='table' AND name = ?",
35
+ )
36
+ .get(name);
37
+ return (row?.n ?? 0) > 0;
38
+ }
39
+
40
+ /** Double-quote a name already proven to exist in `sqlite_master`. */
41
+ export function quoteIdent(name: string): string {
42
+ return `"${name.replace(/"/g, '""')}"`;
43
+ }
44
+
45
+ export interface SqliteTableInfo {
46
+ name: string;
47
+ count: number;
48
+ }
49
+
50
+ /** User tables (FTS shadow + `sqlite_*` internals excluded) with row counts. */
51
+ export function listSqliteTables(db: Database): SqliteTableInfo[] {
52
+ const names = db
53
+ .query<{ name: string }, []>(
54
+ "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name",
55
+ )
56
+ .all()
57
+ .map((r) => r.name);
58
+ return names.map((name) => ({
59
+ name,
60
+ count:
61
+ db.query<{ n: number }, []>(`SELECT COUNT(*) AS n FROM ${quoteIdent(name)}`).get()?.n ?? 0,
62
+ }));
63
+ }
64
+
65
+ export interface SqliteTablePage {
66
+ items: Record<string, unknown>[];
67
+ total: number;
68
+ limit: number;
69
+ offset: number;
70
+ }
71
+
72
+ export class UnknownTableError extends Error {
73
+ constructor(public readonly table: string) {
74
+ super(`Unknown table: ${table}`);
75
+ this.name = 'UnknownTableError';
76
+ }
77
+ }
78
+
79
+ /**
80
+ * A clamped, paginated window over one table. Throws {@link UnknownTableError} for a
81
+ * table that isn't in `sqlite_master` (callers map that to a 404).
82
+ */
83
+ export function readSqliteTable(
84
+ db: Database,
85
+ table: string,
86
+ opts: { limit?: number | string; offset?: number | string } = {},
87
+ ): SqliteTablePage {
88
+ if (!isKnownTable(db, table)) throw new UnknownTableError(table);
89
+ const limit = clampLimit(opts.limit, 50);
90
+ const offset = clampOffset(opts.offset);
91
+ const items = db
92
+ .query<Record<string, unknown>, [number, number]>(
93
+ `SELECT * FROM ${quoteIdent(table)} LIMIT ? OFFSET ?`,
94
+ )
95
+ .all(limit, offset);
96
+ const total =
97
+ db.query<{ n: number }, []>(`SELECT COUNT(*) AS n FROM ${quoteIdent(table)}`).get()?.n ?? 0;
98
+ return { items, total, limit, offset };
99
+ }
100
+
101
+ export class NotReadOnlyError extends Error {
102
+ constructor() {
103
+ super('Only read-only SELECT/WITH queries are allowed');
104
+ this.name = 'NotReadOnlyError';
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Run an ad-hoc read-only query with bound params. Throws {@link NotReadOnlyError}
110
+ * for anything the {@link isReadOnlySql} guard rejects (non-SELECT, stacked
111
+ * statements, PRAGMA, …); SQLite errors (bad column/table) propagate as `Error`.
112
+ */
113
+ export function runReadonlySqlite(
114
+ db: Database,
115
+ sql: string,
116
+ params: unknown[] = [],
117
+ ): Record<string, unknown>[] {
118
+ if (typeof sql !== 'string' || !isReadOnlySql(sql)) throw new NotReadOnlyError();
119
+ return db.query(sql).all(...(params as never[])) as Record<string, unknown>[];
120
+ }
@@ -0,0 +1,43 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { describe, expect, test } from 'bun:test';
3
+ import { UnknownTableError } from './browse';
4
+ import { exportSqliteSnapshot, exportSqliteTable } from './export';
5
+
6
+ function seed(): Database {
7
+ const db = new Database(':memory:');
8
+ db.run('CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)');
9
+ db.run("INSERT INTO notes (id, body) VALUES (1, 'a'), (2, 'b, with comma')");
10
+ return db;
11
+ }
12
+
13
+ describe('exportSqliteTable', () => {
14
+ test('csv includes a header and RFC-4180 quoting', () => {
15
+ const out = exportSqliteTable(seed(), 'notes', 'csv');
16
+ expect(out).toMatchObject({ filename: 'notes.csv', mime: 'text/csv' });
17
+ expect(out.text.startsWith('id,body')).toBe(true);
18
+ expect(out.text).toContain('"b, with comma"');
19
+ });
20
+
21
+ test('json round-trips the rows', () => {
22
+ const out = exportSqliteTable(seed(), 'notes', 'json');
23
+ expect(out).toMatchObject({ filename: 'notes.json', mime: 'application/json' });
24
+ expect(JSON.parse(out.text)).toEqual([
25
+ { id: 1, body: 'a' },
26
+ { id: 2, body: 'b, with comma' },
27
+ ]);
28
+ });
29
+
30
+ test('throws UnknownTableError for a bad table', () => {
31
+ expect(() => exportSqliteTable(seed(), 'nope', 'csv')).toThrow(UnknownTableError);
32
+ });
33
+ });
34
+
35
+ describe('exportSqliteSnapshot', () => {
36
+ test('produces re-openable .sqlite bytes carrying the data', () => {
37
+ const snap = exportSqliteSnapshot(seed());
38
+ expect(snap.filename.endsWith('.sqlite')).toBe(true);
39
+ expect(snap.bytes.byteLength).toBeGreaterThan(0);
40
+ // SQLite files start with the "SQLite format 3\0" magic.
41
+ expect(new TextDecoder().decode(snap.bytes.slice(0, 15))).toBe('SQLite format 3');
42
+ });
43
+ });
@@ -0,0 +1,74 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import { readFileSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { toCsv } from 'cwip/csv';
6
+ import { isKnownTable, quoteIdent, UnknownTableError } from './browse';
7
+
8
+ /**
9
+ * Export primitives for `bun:sqlite` — the missing "get the data out" half of the
10
+ * SQLite toolkit. Table export composes the ONE canonical serializers (`toCsv` from
11
+ * `cwip/csv`, `JSON.stringify` for JSON — no third CSV/JSON path); snapshot export
12
+ * reuses the `VACUUM INTO` primitive (see `backup.ts`) to produce a portable `.sqlite`.
13
+ *
14
+ * These return in-memory payloads (text / bytes) rather than streaming to a Response,
15
+ * so they're transport-agnostic: a cursedshell plugin returns them over its JSON RPC
16
+ * (base64 for bytes), a Hono app streams them as a `Content-Disposition: attachment`
17
+ * download. Admin-scale — a whole-DB snapshot is base64'd through JSON, fine for
18
+ * typical DB sizes; very large DBs should stream a file route instead.
19
+ */
20
+
21
+ export type TableExportFormat = 'csv' | 'json';
22
+
23
+ export interface TableExport {
24
+ filename: string;
25
+ mime: string;
26
+ text: string;
27
+ }
28
+
29
+ /** All rows of one table serialized to CSV or JSON. Throws {@link UnknownTableError}. */
30
+ export function exportSqliteTable(
31
+ db: Database,
32
+ table: string,
33
+ format: TableExportFormat,
34
+ ): TableExport {
35
+ if (!isKnownTable(db, table)) throw new UnknownTableError(table);
36
+ const rows = db.query<Record<string, unknown>, []>(`SELECT * FROM ${quoteIdent(table)}`).all();
37
+ if (format === 'json') {
38
+ return {
39
+ filename: `${table}.json`,
40
+ mime: 'application/json',
41
+ text: JSON.stringify(rows, null, 2),
42
+ };
43
+ }
44
+ return {
45
+ filename: `${table}.csv`,
46
+ mime: 'text/csv',
47
+ // toCsv needs at least the columns; an empty table yields an empty string, which
48
+ // is a valid (header-less) CSV for a zero-row export.
49
+ text: toCsv(rows),
50
+ };
51
+ }
52
+
53
+ export interface SnapshotExport {
54
+ filename: string;
55
+ mime: string;
56
+ bytes: Uint8Array;
57
+ }
58
+
59
+ /**
60
+ * A compact, WAL-merged copy of the whole database as `.sqlite` bytes, via
61
+ * `VACUUM INTO` a throwaway temp file (the same primitive `createSqliteBackupService`
62
+ * uses). The temp file is always removed.
63
+ */
64
+ export function exportSqliteSnapshot(db: Database): SnapshotExport {
65
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
66
+ const tmp = join(tmpdir(), `cb-export-${stamp}-${Math.random().toString(36).slice(2)}.sqlite`);
67
+ try {
68
+ db.run('VACUUM INTO ?', [tmp]);
69
+ const bytes = new Uint8Array(readFileSync(tmp));
70
+ return { filename: `snapshot-${stamp}.sqlite`, mime: 'application/x-sqlite3', bytes };
71
+ } finally {
72
+ rmSync(tmp, { force: true });
73
+ }
74
+ }