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,111 @@
1
+ /**
2
+ * `mountShareRoutes` — the three HTTP routes a shareable resource needs, once.
3
+ *
4
+ * ── Why this exists ─────────────────────────────────────────────────────────────────────
5
+ * The store (`./store.ts`) was already the fleet's one sharing model; the ROUTES over it
6
+ * were not. `apps/life` wrote them once and mounted them four times (calendars, lists,
7
+ * to-dos, board cards) and `apps/family` wrote a fifth copy for people — five hand-rolled
8
+ * copies of read/grant/revoke, each re-deciding the same three policy questions:
9
+ *
10
+ * 1. **All three routes are OWNER-only, including the READ.** "Who else can see this" is
11
+ * not part of what "shared with you" grants — a recipient must never reach
12
+ * `listForResource`, because the roster of other grantees is itself a disclosure.
13
+ * Five copies is five chances to write the gate on POST/DELETE and forget it on GET.
14
+ * 2. **`group:<id>` is how a group subject travels in a URL.** A path segment cannot
15
+ * carry a `{type, id}` pair, so the prefix IS the type. Getting that wrong revokes a
16
+ * USER grant whose address happens to start with `group:` — or, worse, silently
17
+ * revokes nothing and reports success.
18
+ * 3. **A grant only ever widens, and the audit trail outlives it.** Both are the store's,
19
+ * and both are only true if every route actually goes through the store.
20
+ *
21
+ * A bug in any of those has to be fixable by one commit. That is the placement test in
22
+ * `cursedbelt/STANDARDS.md`, and it passes here.
23
+ *
24
+ * ── 🔴 The wire shape is LIFE's, and family's client changed to match ────────────────────
25
+ * The two apps genuinely disagreed and one had to give:
26
+ *
27
+ * · revoke — family put the subject in the QUERY (`DELETE …/shares?subject=&type=`),
28
+ * life in the PATH (`DELETE …/shares/:subject`, `group:` prefixed). The path wins.
29
+ * family's own comment argued for the query on the grounds that the target should be
30
+ * visible in the request line — which a path segment satisfies just as well, while
31
+ * also being the shape four of the five mounts already used. Changing one client beat
32
+ * changing four.
33
+ * · read — family returned `{grants, events, labels}`, life `{grants, events, groups}`.
34
+ * Neither extra is general: `labels` resolves addresses to names out of the family
35
+ * tree *inside the viewer's space*, `groups` is life's household roster. So the
36
+ * primitive owns `{grants, events}` and an app folds its own half in through
37
+ * {@link ShareRouterOptions.extra}.
38
+ *
39
+ * ── What stays in the app ───────────────────────────────────────────────────────────────
40
+ * Ownership. `mustOwn` is the app's, because "who owns a calendar" and "who may curate a
41
+ * family file" are policy this module has no business knowing — and because the 404/403
42
+ * ORDER is policy too (family answers 404 before 403 on a person the viewer may not know
43
+ * exists). The hook returns the refusal it wants, or null to continue.
44
+ */
45
+ import type { Context, Hono } from 'hono';
46
+ import type { ShareEvent, ShareGrant } from 'cursedbelt-core/sharing/model';
47
+ import type { ShareStore } from './store';
48
+ /** The `group:` prefix that lets a group subject travel in a path segment. Exported so a
49
+ * client and a route cannot disagree about it. */
50
+ export declare const GROUP_SUBJECT_PREFIX = "group:";
51
+ /** Split a URL subject segment into the store's `{type, id}` pair. */
52
+ export declare function parseSubjectParam(raw: string): {
53
+ type: 'user' | 'group';
54
+ id: string;
55
+ };
56
+ /** The inverse — what a client puts in the path. */
57
+ export declare const subjectParam: (subject: {
58
+ type: "user" | "group";
59
+ id: string;
60
+ }) => string;
61
+ /** What every route needs about who is asking. Structural, so an app hands its own viewer
62
+ * type straight in. */
63
+ export interface ShareRouterViewer {
64
+ /** The fleet address; the subject id of every `user` grant. */
65
+ email: string;
66
+ }
67
+ export interface ShareRouterOptions<V extends ShareRouterViewer> {
68
+ /**
69
+ * The resource path, with `:id` in it — e.g. `/api/calendars/:id`. The three routes
70
+ * hang off `${base}/shares`; revoke adds `/:subject`.
71
+ */
72
+ base: string;
73
+ /** The `kind` half of the store's `(app, kind, id)` triple. */
74
+ kind: string;
75
+ /** The store. A function when it is per-request (a multi-tenant app). */
76
+ shares: ShareStore | ((c: Context) => ShareStore);
77
+ /** Who is asking. Returning null refuses every route with 401. */
78
+ viewerOf: (c: Context) => V | null;
79
+ /**
80
+ * 🔴 The OWNER gate, run on ALL THREE routes including the read. Return a `Response` to
81
+ * refuse (the app owns the status AND the 404-before-403 ordering), or null to allow.
82
+ * `throw`ing is fine too — an app with an error middleware can keep using it.
83
+ */
84
+ mustOwn: (c: Context, id: string, viewer: V) => Response | null | Promise<Response | null>;
85
+ /** Extra fields folded into the READ payload beside `{grants, events}` — family's
86
+ * `labels`, life's `groups`. */
87
+ extra?: (c: Context, id: string, viewer: V) => Record<string, unknown>;
88
+ /** How many audit lines the read carries. Default 50. */
89
+ trailLimit?: number;
90
+ }
91
+ /** The read payload's guaranteed half. An app's `extra` is spread in beside it. */
92
+ export interface ShareRoutesPayload {
93
+ grants: ShareGrant[];
94
+ /** Newest first, and it OUTLIVES the grants — a revoked share still has a line. */
95
+ events: ShareEvent[];
96
+ }
97
+ /**
98
+ * Mount GET / POST / DELETE for one `kind` on one app.
99
+ *
100
+ * ```ts
101
+ * mountShareRoutes(app, {
102
+ * base: '/api/calendars/:id',
103
+ * kind: CALENDAR_KIND,
104
+ * shares,
105
+ * viewerOf,
106
+ * mustOwn: (c, id, viewer) => (ownsCalendar(db, viewer, id) ? null : c.json({ error: 'not found' }, 404)),
107
+ * extra: () => ({ groups: shares.listGroups() }),
108
+ * });
109
+ * ```
110
+ */
111
+ export declare function mountShareRoutes<V extends ShareRouterViewer>(app: Hono<any>, options: ShareRouterOptions<V>): void;
@@ -0,0 +1,93 @@
1
+ /** The `group:` prefix that lets a group subject travel in a path segment. Exported so a
2
+ * client and a route cannot disagree about it. */
3
+ export const GROUP_SUBJECT_PREFIX = 'group:';
4
+ /** Split a URL subject segment into the store's `{type, id}` pair. */
5
+ export function parseSubjectParam(raw) {
6
+ const decoded = decodeURIComponent(raw);
7
+ return decoded.startsWith(GROUP_SUBJECT_PREFIX)
8
+ ? { type: 'group', id: decoded.slice(GROUP_SUBJECT_PREFIX.length) }
9
+ : { type: 'user', id: decoded };
10
+ }
11
+ /** The inverse — what a client puts in the path. */
12
+ export const subjectParam = (subject) => encodeURIComponent(subject.type === 'group' ? GROUP_SUBJECT_PREFIX + subject.id : subject.id);
13
+ const isLevel = (v) => v === 'read' || v === 'write';
14
+ /**
15
+ * Mount GET / POST / DELETE for one `kind` on one app.
16
+ *
17
+ * ```ts
18
+ * mountShareRoutes(app, {
19
+ * base: '/api/calendars/:id',
20
+ * kind: CALENDAR_KIND,
21
+ * shares,
22
+ * viewerOf,
23
+ * mustOwn: (c, id, viewer) => (ownsCalendar(db, viewer, id) ? null : c.json({ error: 'not found' }, 404)),
24
+ * extra: () => ({ groups: shares.listGroups() }),
25
+ * });
26
+ * ```
27
+ */
28
+ export function mountShareRoutes(
29
+ // biome-ignore lint/suspicious/noExplicitAny: the caller's Hono env is theirs, and this
30
+ // module reads nothing off it — narrowing it would force every app to name its own Env
31
+ // here for no gain.
32
+ app, options) {
33
+ const { base, kind, viewerOf, mustOwn, extra, trailLimit = 50 } = options;
34
+ const storeOf = (c) => typeof options.shares === 'function' ? options.shares(c) : options.shares;
35
+ /** The three-step preamble every route shares: identity, id, ownership. */
36
+ async function enter(c) {
37
+ const viewer = viewerOf(c);
38
+ if (!viewer)
39
+ return { refused: c.json({ error: 'sign in first' }, 401) };
40
+ const id = c.req.param('id') ?? '';
41
+ const refused = await mustOwn(c, id, viewer);
42
+ if (refused)
43
+ return { refused };
44
+ return { id, viewer, shares: storeOf(c) };
45
+ }
46
+ app.get(`${base}/shares`, async (c) => {
47
+ const step = await enter(c);
48
+ if ('refused' in step)
49
+ return step.refused;
50
+ const { id, viewer, shares } = step;
51
+ const payload = {
52
+ // The store's own rows, straight through — the sharing panel consumes exactly
53
+ // these, and a DTO in the middle is a place for a field to go missing.
54
+ grants: shares.listForResource({ kind, id }),
55
+ events: shares.events({ kind, id, limit: trailLimit }),
56
+ ...(extra ? extra(c, id, viewer) : {}),
57
+ };
58
+ return c.json(payload);
59
+ });
60
+ app.post(`${base}/shares`, async (c) => {
61
+ const step = await enter(c);
62
+ if ('refused' in step)
63
+ return step.refused;
64
+ const { id, viewer, shares } = step;
65
+ const body = (await c.req.json().catch(() => ({})));
66
+ const subjectId = (body.subject?.id ?? body.email ?? '').trim();
67
+ if (!subjectId)
68
+ return c.json({ error: 'who should this be shared with?' }, 400);
69
+ if (body.level !== undefined && !isLevel(body.level)) {
70
+ return c.json({ error: "level must be 'read' or 'write'" }, 400);
71
+ }
72
+ const grant = shares.grant({
73
+ kind,
74
+ id,
75
+ subject: { type: body.subject?.type === 'group' ? 'group' : 'user', id: subjectId },
76
+ level: isLevel(body.level) ? body.level : 'read',
77
+ grantedBy: viewer.email,
78
+ ...(typeof body.note === 'string' ? { note: body.note } : {}),
79
+ });
80
+ return c.json(grant, 201);
81
+ });
82
+ app.delete(`${base}/shares/:subject`, async (c) => {
83
+ const step = await enter(c);
84
+ if ('refused' in step)
85
+ return step.refused;
86
+ const { id, viewer, shares } = step;
87
+ const raw = c.req.param('subject') ?? '';
88
+ if (!raw.trim())
89
+ return c.json({ error: 'which share should be removed?' }, 400);
90
+ shares.revoke({ kind, id, subject: parseSubjectParam(raw), revokedBy: viewer.email });
91
+ return c.json({ ok: true });
92
+ });
93
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The half a consuming app needs INSIDE its own queries: a SQL boolean saying
3
+ * "this row is shared with the viewer".
4
+ *
5
+ * ── 🔴 Why a SQL fragment and not a list of ids ──────────────────────────────
6
+ * The obvious API is `sharedIds(kind, viewer): string[]`, and it is wrong for the
7
+ * consumers this exists to serve. `apps/family`'s rule, written in `people.ts`
8
+ * before this primitive existed, is that a row the viewer may not see *"must not
9
+ * surface as a snippet, a COUNT, or a tree node either"* — so its visibility test
10
+ * has to be a WHERE clause that a `COUNT(*)`, a JOIN and a recursive tree walk all
11
+ * inherit. Handing the app an array means the app writes `id IN (…)` itself, in
12
+ * every one of those places, with its own quoting; the first one somebody forgets
13
+ * is a disclosure, and it will be the count.
14
+ *
15
+ * So the primitive emits the clause. `sharedResourceSql` returns a correlated
16
+ * `EXISTS (…)` over the grant tables that can be dropped into any query where the
17
+ * resource's id column is in scope, including one whose only bound parameter is
18
+ * something else entirely.
19
+ *
20
+ * ── 🔴 …which means this module quotes literals, so it does it in ONE place ───
21
+ * A fragment with no bindable parameters has to inline its values. Every value
22
+ * that reaches SQL here goes through {@link sqlLiteral} (double every apostrophe —
23
+ * `o'brien@example.com` is an ordinary address) or {@link sqlColumn} (an
24
+ * identifier, matched against a strict pattern and REFUSED otherwise, so a column
25
+ * expression can never smuggle a subquery in). Both are exported and both are
26
+ * tested with hostile input; a consumer never quotes anything itself.
27
+ */
28
+ import { type ShareLevel } from "cursedbelt-core/sharing/model";
29
+ /**
30
+ * Quote a string as a SQL literal.
31
+ *
32
+ * SQLite escapes an apostrophe by doubling it, and that is the whole rule for a
33
+ * TEXT literal — there is no backslash escape to worry about. A NUL byte is
34
+ * refused rather than quoted: SQLite truncates a string at one, so a value
35
+ * containing it would compare equal to a shorter value and silently widen a grant.
36
+ */
37
+ export declare function sqlLiteral(value: string): string;
38
+ export declare function sqlColumn(column: string): string;
39
+ export interface SharedResourceSqlOptions {
40
+ /** The satellite the resource belongs to — the `app` of the `(app, kind, id)`
41
+ * triple. */
42
+ app: string;
43
+ /** The app's own word for this sort of record ("person", "budget"). */
44
+ kind: string;
45
+ /** The viewer's fleet address. An EMPTY id yields `0=1` — see below. */
46
+ subjectId: string;
47
+ /** The column holding the resource's id, in scope of the query being built. */
48
+ column: string;
49
+ /** Minimum level the grant must carry. Default `read`. */
50
+ atLeast?: ShareLevel;
51
+ }
52
+ /**
53
+ * A SQL boolean: true for a row the viewer holds a grant on, at `atLeast` or
54
+ * above, directly or through a group.
55
+ *
56
+ * The group half is a nested `SELECT` rather than a pre-resolved list, so a
57
+ * membership change takes effect on the next query with no request-scoped cache to
58
+ * invalidate — the failure mode of caching it is a relative who was removed from
59
+ * the family group this morning and can still read until something restarts.
60
+ *
61
+ * 🔴 **An empty subject id yields `0=1`, never `1=1`.** An unauthenticated or
62
+ * machine caller has no address, and the arithmetic of `subject_id = ''` against a
63
+ * table where nothing has an empty id happens to be safe today — but a single
64
+ * malformed row would turn that into "shared with everybody". Failing closed on the
65
+ * missing identity is the property, not a coincidence of the data.
66
+ */
67
+ export declare function sharedResourceSql(options: SharedResourceSqlOptions): string;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The half a consuming app needs INSIDE its own queries: a SQL boolean saying
3
+ * "this row is shared with the viewer".
4
+ *
5
+ * ── 🔴 Why a SQL fragment and not a list of ids ──────────────────────────────
6
+ * The obvious API is `sharedIds(kind, viewer): string[]`, and it is wrong for the
7
+ * consumers this exists to serve. `apps/family`'s rule, written in `people.ts`
8
+ * before this primitive existed, is that a row the viewer may not see *"must not
9
+ * surface as a snippet, a COUNT, or a tree node either"* — so its visibility test
10
+ * has to be a WHERE clause that a `COUNT(*)`, a JOIN and a recursive tree walk all
11
+ * inherit. Handing the app an array means the app writes `id IN (…)` itself, in
12
+ * every one of those places, with its own quoting; the first one somebody forgets
13
+ * is a disclosure, and it will be the count.
14
+ *
15
+ * So the primitive emits the clause. `sharedResourceSql` returns a correlated
16
+ * `EXISTS (…)` over the grant tables that can be dropped into any query where the
17
+ * resource's id column is in scope, including one whose only bound parameter is
18
+ * something else entirely.
19
+ *
20
+ * ── 🔴 …which means this module quotes literals, so it does it in ONE place ───
21
+ * A fragment with no bindable parameters has to inline its values. Every value
22
+ * that reaches SQL here goes through {@link sqlLiteral} (double every apostrophe —
23
+ * `o'brien@example.com` is an ordinary address) or {@link sqlColumn} (an
24
+ * identifier, matched against a strict pattern and REFUSED otherwise, so a column
25
+ * expression can never smuggle a subquery in). Both are exported and both are
26
+ * tested with hostile input; a consumer never quotes anything itself.
27
+ */
28
+ import { levelsMeeting, normalizeSubjectId } from "cursedbelt-core/sharing/model";
29
+ import { SHARE_GRANTS_TABLE, SHARE_GROUP_MEMBERS_TABLE } from "./migrations";
30
+ /**
31
+ * Quote a string as a SQL literal.
32
+ *
33
+ * SQLite escapes an apostrophe by doubling it, and that is the whole rule for a
34
+ * TEXT literal — there is no backslash escape to worry about. A NUL byte is
35
+ * refused rather than quoted: SQLite truncates a string at one, so a value
36
+ * containing it would compare equal to a shorter value and silently widen a grant.
37
+ */
38
+ export function sqlLiteral(value) {
39
+ if (value.includes("\u0000")) {
40
+ throw new Error("[cursedbelt-core/sharing] a SQL literal may not contain a NUL byte");
41
+ }
42
+ return `'${value.replaceAll("'", "''")}'`;
43
+ }
44
+ /** A column reference — `id`, `p.id`, `"people"."id"` is NOT accepted. Anything
45
+ * that is not a bare (optionally table-qualified) identifier is refused, because
46
+ * this value is interpolated and a caller passing a computed string is how an
47
+ * injection reaches a fragment builder. */
48
+ const COLUMN_PATTERN = /^[A-Za-z_][A-Za-z0-9_$]*(\.[A-Za-z_][A-Za-z0-9_$]*)?$/;
49
+ export function sqlColumn(column) {
50
+ if (!COLUMN_PATTERN.test(column)) {
51
+ throw new Error(`[cursedbelt-core/sharing] ${JSON.stringify(column)} is not a plain column reference ` +
52
+ "(expected `id` or `alias.id`)");
53
+ }
54
+ return column;
55
+ }
56
+ /**
57
+ * A SQL boolean: true for a row the viewer holds a grant on, at `atLeast` or
58
+ * above, directly or through a group.
59
+ *
60
+ * The group half is a nested `SELECT` rather than a pre-resolved list, so a
61
+ * membership change takes effect on the next query with no request-scoped cache to
62
+ * invalidate — the failure mode of caching it is a relative who was removed from
63
+ * the family group this morning and can still read until something restarts.
64
+ *
65
+ * 🔴 **An empty subject id yields `0=1`, never `1=1`.** An unauthenticated or
66
+ * machine caller has no address, and the arithmetic of `subject_id = ''` against a
67
+ * table where nothing has an empty id happens to be safe today — but a single
68
+ * malformed row would turn that into "shared with everybody". Failing closed on the
69
+ * missing identity is the property, not a coincidence of the data.
70
+ */
71
+ export function sharedResourceSql(options) {
72
+ const subjectId = normalizeSubjectId("user", options.subjectId);
73
+ if (!subjectId)
74
+ return "0=1";
75
+ const column = sqlColumn(options.column);
76
+ const levels = levelsMeeting(options.atLeast ?? "read")
77
+ .map((level) => sqlLiteral(level))
78
+ .join(", ");
79
+ const subject = sqlLiteral(subjectId);
80
+ return (`EXISTS (SELECT 1 FROM ${SHARE_GRANTS_TABLE} sg` +
81
+ ` WHERE sg.app = ${sqlLiteral(options.app)}` +
82
+ ` AND sg.kind = ${sqlLiteral(options.kind)}` +
83
+ ` AND sg.resource_id = ${column}` +
84
+ ` AND sg.level IN (${levels})` +
85
+ ` AND ((sg.subject_type = 'user' AND sg.subject_id = ${subject})` +
86
+ ` OR (sg.subject_type = 'group' AND sg.subject_id IN` +
87
+ ` (SELECT gm.group_id FROM ${SHARE_GROUP_MEMBERS_TABLE} gm WHERE gm.subject_id = ${subject}))))`);
88
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * The store: grants, groups, revocation and the audit trail, over `bun:sqlite`.
3
+ *
4
+ * ── How an app uses it ──────────────────────────────────────────────────────
5
+ * ```ts
6
+ * const shares = createShareStore({ db, app: "family", onEvent: notify });
7
+ *
8
+ * shares.grant({ kind: "person", id, subject: user("aunt@example.com"),
9
+ * level: "read", grantedBy: viewer.email });
10
+ * shares.accessFor({ kind: "person", id }, { subjectId: viewer.email }); // "read"
11
+ * shares.revoke({ kind: "person", id, subject: user("aunt@example.com"),
12
+ * revokedBy: viewer.email });
13
+ * ```
14
+ * …and inside the app's own queries, `sharedResourceSql` (see `./sql.ts`) is the
15
+ * WHERE clause, so a filtered row never surfaces as a count either.
16
+ *
17
+ * ── 🔴 Five properties this file exists to guarantee ────────────────────────
18
+ * 1. **Every mutation writes its own audit row, in the same transaction.** There
19
+ * is no `recordEvent` in the public API and no way to grant without logging.
20
+ * An audit trail a caller has to remember to write is a trail with holes
21
+ * exactly where somebody was in a hurry.
22
+ * 2. **A revoked grant's history survives it.** Revocation deletes the grant row
23
+ * and appends an event; nothing here deletes an event, ever.
24
+ * 3. **Deleting the RESOURCE is a revocation, not an orphan.** `revokeResource`
25
+ * exists so an app deleting a row has one call to make. A grant left behind
26
+ * for a deleted id silently becomes access to whatever later reuses that id.
27
+ * 4. **The notification hook cannot break a write.** `onEvent` runs AFTER the
28
+ * transaction commits and its throw is swallowed with a warning: a mail server
29
+ * being down must not lose the grant the owner just made.
30
+ * 5. **A subject is normalized once, here.** Callers pass whatever the form gave
31
+ * them; `normalizeSubjectId` is applied on the way in AND on the way to a
32
+ * lookup, so a grant made to `" Ameena@Example.com "` is found by the session
33
+ * that signs in as `ameena@example.com`.
34
+ */
35
+ import type { Database } from "bun:sqlite";
36
+ import { type ShareAccess, type ShareEvent, type ShareGrant, type ShareGroup, type ShareGroupMember, type ShareLevel, type ShareSubject, type ShareViewer } from "cursedbelt-core/sharing/model";
37
+ /** Shorthand for the common subject. `group(id)` is its sibling. */
38
+ export declare const user: (id: string) => ShareSubject;
39
+ export declare const group: (id: string) => ShareSubject;
40
+ /** A resource within the store's own app — the `app` half is supplied by the
41
+ * store, so a caller cannot mistakenly write a grant into another app's rows. */
42
+ export interface ShareTarget {
43
+ kind: string;
44
+ id: string;
45
+ }
46
+ export interface ShareStoreOptions {
47
+ db: Database;
48
+ /** The satellite this store speaks for. Fixed at construction so `app` cannot
49
+ * be got wrong per call — the commonest way a grant becomes invisible. */
50
+ app: string;
51
+ /**
52
+ * Notified after every committed mutation — the notification hook ca's model
53
+ * had, as a seam rather than a dependency.
54
+ *
55
+ * 🔴 It stays a seam now that the other half EXISTS. `cursedbelt/server/
56
+ * notifications` landed the same day this did, so a consumer can wire the two
57
+ * in one line — `onEvent: (e) => notifications.notify({ userId: e.subject.id,
58
+ * … })` — and `describeShareEvent` in the vocabulary is the wording both the
59
+ * panel and that notification use. What must NOT happen is this store
60
+ * importing that one: an app is entitled to a sharing model with no bell (the
61
+ * grant is still audited), the subject of a GROUP grant is a group id and not
62
+ * a person to notify, and an app knows which of its users to address where
63
+ * this primitive only knows a subject id.
64
+ */
65
+ onEvent?: (event: ShareEvent) => void;
66
+ /** Injectable clock, for tests that assert ordering. ISO-8601 strings. */
67
+ now?: () => string;
68
+ }
69
+ export interface GrantInput extends ShareTarget {
70
+ subject: ShareSubject;
71
+ level: ShareLevel;
72
+ grantedBy: string;
73
+ note?: string;
74
+ }
75
+ export interface RevokeInput extends ShareTarget {
76
+ subject: ShareSubject;
77
+ revokedBy: string;
78
+ note?: string;
79
+ }
80
+ export interface ShareEventQuery {
81
+ kind?: string;
82
+ id?: string;
83
+ subject?: ShareSubject;
84
+ /** Newest first, capped. Default 200 — a panel shows a page, and an unbounded
85
+ * audit read on a busy resource is the query that times out in production. */
86
+ limit?: number;
87
+ }
88
+ export type ShareStore = ReturnType<typeof createShareStore>;
89
+ /**
90
+ * Open (and migrate) the sharing tables in this database, returning the store.
91
+ *
92
+ * Idempotent: safe to call once per boot per `db`, and safe to call in a database
93
+ * that already has the tables (the migration runner records what it applied).
94
+ */
95
+ export declare function createShareStore(options: ShareStoreOptions): {
96
+ /** The app this store speaks for — exposed so a consumer can build its own
97
+ * `sharedResourceSql` call without re-declaring the name. */
98
+ app: string;
99
+ /**
100
+ * Give `subject` access to a resource, or change the access they have.
101
+ *
102
+ * An unchanged re-grant is still recorded as a `grant` event: "the owner
103
+ * confirmed this on Tuesday" is a fact somebody reading the trail wants, and
104
+ * suppressing it would make an idempotent API silently unauditable.
105
+ */
106
+ grant(input: GrantInput): ShareGrant;
107
+ /** Take access away. Returns the level that was removed, or null when there
108
+ * was nothing to remove — in which case nothing is logged, because an audit
109
+ * trail full of "revoked nothing" is one nobody reads. */
110
+ revoke(input: RevokeInput): ShareLevel | null;
111
+ /**
112
+ * Revoke EVERY grant on one resource — what an app calls when it deletes the
113
+ * record. Returns how many were removed.
114
+ *
115
+ * 🔴 Not optional housekeeping. Resource ids are app-minted, and a grant left
116
+ * behind for a deleted id becomes access to whatever is created with that id
117
+ * later. `apps/family` calls this in the same transaction as deleting a
118
+ * person.
119
+ */
120
+ revokeResource(target: ShareTarget, revokedBy: string): number;
121
+ /** Revoke everything shared with one subject, across every kind — the
122
+ * offboarding call. A person losing their fleet account keeps nothing. */
123
+ revokeSubject(subject: ShareSubject, revokedBy: string): number;
124
+ /** Who can see this record, strongest first then oldest — the panel's read. */
125
+ listForResource(target: ShareTarget): ShareGrant[];
126
+ /** Everything shared with one subject — "shared with you". Group grants are
127
+ * NOT expanded here; use {@link resourceIdsFor} for the resolved answer. */
128
+ listForSubject(subject: ShareSubject): ShareGrant[];
129
+ /**
130
+ * What may this viewer do with this resource — the resolver.
131
+ *
132
+ * The strongest of their own grant and every group they are in, computed in
133
+ * SQL. `resolveAccess` in `cursedbelt-core/sharing` is the same rule written as a
134
+ * pure function, and `store.spec.ts` checks the two agree.
135
+ */
136
+ accessFor(target: ShareTarget, viewer: ShareViewer): ShareAccess;
137
+ /** Every resource of `kind` this viewer reaches at `atLeast` or above.
138
+ * For filtering rows in the app's OWN query, prefer `sharedResourceSql`. */
139
+ resourceIdsFor(kind: string, viewer: ShareViewer, atLeast?: ShareLevel): string[];
140
+ /** The WHERE-clause half, bound to this store's app. See `./sql.ts` for why
141
+ * a fragment rather than a list of ids. */
142
+ sharedSql(input: {
143
+ kind: string;
144
+ subjectId: string;
145
+ column: string;
146
+ atLeast?: ShareLevel;
147
+ }): string;
148
+ createGroup(input: {
149
+ id: string;
150
+ name: string;
151
+ createdBy: string;
152
+ note?: string;
153
+ }): ShareGroup;
154
+ renameGroup(id: string, name: string, actor: string): void;
155
+ /**
156
+ * Delete a group — and every grant made TO it.
157
+ *
158
+ * 🔴 The grants go with it, explicitly, each with its own revoke event. The
159
+ * `ON DELETE CASCADE` on membership does not reach `share_grants` (a group
160
+ * subject is a string, not a foreign key, because the same column holds user
161
+ * addresses), so a group deleted without this leaves rows nobody can see in
162
+ * any UI and nothing can revoke.
163
+ */
164
+ deleteGroup(id: string, actor: string): void;
165
+ listGroups(): ShareGroup[];
166
+ addGroupMember(groupId: string, subjectId: string, actor: string): void;
167
+ removeGroupMember(groupId: string, subjectId: string, actor: string): void;
168
+ listGroupMembers(groupId: string): ShareGroupMember[];
169
+ /** Which groups is this person in — the one place membership is resolved. */
170
+ groupIdsFor(subjectId: string): string[];
171
+ /** The trail, newest first. Filter by resource, by subject, or neither. */
172
+ events(query?: ShareEventQuery): ShareEvent[];
173
+ };