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,421 @@
1
+ /**
2
+ * `createPgSearchProvider` — the SAAS profile's ranked-retrieval tier (04 §3,
3
+ * audit gap L11 and frontier item 4(c)).
4
+ *
5
+ * Before this file, the saas profile had **no search tier at all**. `bootEngineApp`
6
+ * derived its `SearchProvider` from the local SQLite handles it does not open
7
+ * when a host injects storage, so under saas `search` was `undefined`, the
8
+ * `reindex` job fell through to the loud "needs a wired SearchProvider"
9
+ * placeholder, and `KnowledgeService.search` took its `LIKE` fallback for every
10
+ * tenant on the platform. 04 §1's gate text — "a leased `reindex` executing
11
+ * through the pg-dialect repo" — was unachievable, and the `genAppSaasSmoke`
12
+ * job leg asserted that the placeholder failed, as designed. This is the far
13
+ * side that makes both of those real.
14
+ *
15
+ * ## What is shared with the local provider, and what is not
16
+ *
17
+ * **The vector tier is shared, exactly.** Chunking, the little-endian embedding
18
+ * codec, the revision guard, the comparability predicates, the cosine rank and
19
+ * the reciprocal-rank fusion all come from `cwip`'s `searchVectorTier` — one
20
+ * implementation, driven here through a Postgres executor and there through a
21
+ * SQLite one. A second copy of ranking math is the worst kind of duplication:
22
+ * both copies keep returning plausible results after they diverge.
23
+ *
24
+ * **The keyword tier is NOT shared, and cannot be.** SQLite's is an FTS5
25
+ * external-content virtual table maintained by triggers; this one is a
26
+ * `tsvector` generated column with a GIN index. `domains/search.ts` already
27
+ * refuses to compile its chain for Postgres and says why. What IS held identical
28
+ * is the OBSERVABLE contract: the same 4× title weighting (`PG_RANK_WEIGHTS`
29
+ * against `bm25(search_fts, 4.0, 1.0)`), the same "higher score is better" sign,
30
+ * the same tenancy-as-a-predicate law, and the same behavior on a user typing
31
+ * punctuation into a search box. Two profiles that ranked one corpus differently
32
+ * would make "search ranks wrong in production" unreproducible on a laptop.
33
+ *
34
+ * ## Two things Postgres makes better, and one it makes worse
35
+ *
36
+ * **Better: a search box is not a query language, and here it is not a hazard
37
+ * either.** The SQLite provider catches an FTS5 syntax error and retries the
38
+ * whole input as a quoted phrase literal. `websearch_to_tsquery` cannot raise a
39
+ * syntax error on any input — quotes, `-`, `OR`, an unbalanced paren are all
40
+ * defined — so the retry has nothing to catch and does not exist. That is a real
41
+ * difference in the failure surface, not a stylistic one.
42
+ *
43
+ * **Better: the shadow cannot drift.** `tsv` is `GENERATED ALWAYS AS … STORED`,
44
+ * so no code path writes it — see `pgSearchSchema.ts`.
45
+ *
46
+ * **Worse: the connection accounting.** Every operation checks a connection out
47
+ * of the ONE pool, opens a transaction, scopes it to the tenant and returns it.
48
+ * The vector tier's chunk writes go through a per-statement scoped executor, so
49
+ * writing an eight-chunk document is eight checkouts rather than one. That is
50
+ * deliberate: the alternative is holding a pooled connection and an open
51
+ * transaction across eight embedding round-trips, which is how a search backlog
52
+ * takes an entire deployment's Postgres down. The chunk write is already
53
+ * dominated by inference latency; the checkout is noise beside it.
54
+ *
55
+ * ## The ceiling, again stated rather than hidden
56
+ *
57
+ * The vector rank is an exact cosine scan over a bounded candidate set, not an
58
+ * ANN index — no `pgvector`, no extension a customer's managed Postgres may
59
+ * refuse to install. `cwip`'s `searchVectorTier` header carries the numbers and
60
+ * the upgrade path; truncation is REPORTED through `onVectorTruncated` rather
61
+ * than swallowed, because a search tier that quietly degrades as a customer
62
+ * succeeds is worse than one that says so.
63
+ */
64
+ import {
65
+ assertNonEmpty,
66
+ clampLimit,
67
+ clampPositive,
68
+ countChunks,
69
+ docKey,
70
+ fuseSearchTiers,
71
+ rankChunksByVector,
72
+ UNKNOWN_EMBED_MODEL,
73
+ writeDocChunks,
74
+ type RebuildableSearchProvider,
75
+ type SearchDoc,
76
+ type SearchHit,
77
+ type SearchQueryOptions,
78
+ type VectorRankResult,
79
+ } from 'cwip/search-provider';
80
+ import type { ChunkOptions } from 'cwip/search';
81
+ import {
82
+ createPostgresControlExecutor,
83
+ saasSetTenantSql,
84
+ type SaasConnection,
85
+ type SaasPool,
86
+ type Statement,
87
+ type StatementExecutor,
88
+ type WriteResult,
89
+ } from 'cwip/storage';
90
+ import { AppError } from '../errors';
91
+ import { PG_RANK_WEIGHTS } from './pgSearchSchema';
92
+
93
+ const DEFAULT_LIMIT = 20;
94
+ const MAX_LIMIT = 200;
95
+ /** Mirrors the SQLite provider's fusion depth — see its `FUSION_DEPTH`. */
96
+ const FUSION_DEPTH = 4;
97
+ /** `ts_headline` window, in words. Comparable to the SQLite side's 14-token snippet. */
98
+ const SNIPPET_WORDS = 18;
99
+
100
+ export interface PgSearchProviderOptions {
101
+ /** The deployment's ONE pool. Injected — this module opens no socket (law #8). */
102
+ pool: SaasPool;
103
+ defaultLimit?: number;
104
+ maxLimit?: number;
105
+ /**
106
+ * The embedder backing the vector tier. Absent ⇒ the provider has no `embed`
107
+ * method at all and `search_chunks` stays honestly empty — the same capability
108
+ * signal the local provider gives, so a caller's `provider.embed?.(…)` test
109
+ * means the same thing on both profiles.
110
+ */
111
+ embed?(text: string, tenantId: string): Promise<Float32Array>;
112
+ /** Which model `embed` speaks for. Recorded per chunk; a query predicate. */
113
+ embedModel?: string;
114
+ chunkOptions?: ChunkOptions;
115
+ vectorCandidateLimit?: number;
116
+ onVectorTruncated?(detail: { tenantId: string; corpora: readonly string[]; scanned: number }): void;
117
+ /** Where a vector-tier failure goes. The keyword tier is the floor. */
118
+ onVectorError?(error: unknown, phase: 'index' | 'query'): void;
119
+ }
120
+
121
+ interface HitRow {
122
+ id: string;
123
+ corpus: string;
124
+ tenant_id: string;
125
+ title: string;
126
+ snippet: string | null;
127
+ rank: number | string;
128
+ }
129
+
130
+ interface DocIdentity {
131
+ id: string | number;
132
+ rev: number | string;
133
+ }
134
+
135
+ interface DocMetaRow {
136
+ corpus: string;
137
+ source_id: string;
138
+ title: string;
139
+ }
140
+
141
+ export function createPgSearchProvider(options: PgSearchProviderOptions): RebuildableSearchProvider {
142
+ const { pool } = options;
143
+ const defaultLimit = clampPositive(options.defaultLimit, DEFAULT_LIMIT);
144
+ const maxLimit = clampPositive(options.maxLimit, MAX_LIMIT);
145
+ const embedModel = options.embedModel ?? UNKNOWN_EMBED_MODEL;
146
+
147
+ /**
148
+ * Run `fn` on a connection scoped to `tenantId` for the duration of ONE
149
+ * transaction.
150
+ *
151
+ * The `BEGIN` is not decoration around a single statement: `set_config(…, true)`
152
+ * is transaction-local, and outside a transaction it would apply to the
153
+ * session and ride the connection back into the pool with another tenant's
154
+ * scope still set. That is the exact leak RLS exists to prevent, so the
155
+ * transaction is what makes the scoping safe rather than what makes it atomic.
156
+ */
157
+ const scoped = async <T>(tenantId: string, fn: (executor: StatementExecutor) => Promise<T>): Promise<T> => {
158
+ assertNonEmpty(tenantId, 'tenantId');
159
+ return await pool.withConnection(async (connection: SaasConnection) => {
160
+ await connection.execute('BEGIN');
161
+ try {
162
+ const setTenant = saasSetTenantSql(tenantId);
163
+ await connection.execute(setTenant.sql, setTenant.params);
164
+ const result = await fn(createPostgresControlExecutor(connection));
165
+ await connection.execute('COMMIT');
166
+ return result;
167
+ } catch (error) {
168
+ // Best-effort: a connection already broken by the failure cannot roll
169
+ // back, and masking the original error with the rollback's would hide
170
+ // the thing worth reading.
171
+ await connection.execute('ROLLBACK').catch(() => undefined);
172
+ throw error;
173
+ }
174
+ });
175
+ };
176
+
177
+ /**
178
+ * An executor whose every statement is its own scoped transaction.
179
+ *
180
+ * This is what the vector tier runs on, and the shape is the point: the tier
181
+ * interleaves database writes with embedding round-trips, so a single
182
+ * long-lived transaction across it would pin a pooled connection for the
183
+ * duration of somebody else's inference queue. Each statement being separately
184
+ * atomic is also exactly what the tier's revision guard assumes — the guard is
185
+ * a `WHERE EXISTS` evaluated at write time, never a read-then-act.
186
+ */
187
+ const perStatementExecutor = (tenantId: string): StatementExecutor => ({
188
+ dialect: 'postgres',
189
+ all: <Row>(statement: Statement): Promise<Row[]> => scoped(tenantId, (exec) => exec.all<Row>(statement)),
190
+ get: <Row>(statement: Statement): Promise<Row | null> => scoped(tenantId, (exec) => exec.get<Row>(statement)),
191
+ write: <Row>(statement: Statement): Promise<WriteResult<Row>> =>
192
+ scoped(tenantId, (exec) => exec.write<Row>(statement)),
193
+ });
194
+
195
+ const keywordQuery = (
196
+ query: string,
197
+ tenantId: string,
198
+ corpora: readonly string[],
199
+ limit: number,
200
+ ): Statement => {
201
+ const where = ['d.tenant_id = ?', "d.tsv @@ websearch_to_tsquery('simple', ?)"];
202
+ const params: unknown[] = [query, query, tenantId, query];
203
+ if (corpora.length > 0) {
204
+ where.push(`d.corpus IN (${corpora.map(() => '?').join(', ')})`);
205
+ params.push(...corpora);
206
+ }
207
+ params.push(limit);
208
+ return {
209
+ // `ts_headline` gets the same empty start/stop markers the SQLite
210
+ // `snippet()` call uses, so a client rendering a hit does not have to know
211
+ // which profile produced it.
212
+ sql: `SELECT d.source_id AS id,
213
+ d.corpus AS corpus,
214
+ d.tenant_id AS tenant_id,
215
+ d.title AS title,
216
+ ts_headline('simple', d.body, websearch_to_tsquery('simple', ?),
217
+ 'MaxWords=${SNIPPET_WORDS}, MinWords=1, ShortWord=1, HighlightAll=false, MaxFragments=1, StartSel="", StopSel=""') AS snippet,
218
+ ts_rank('${PG_RANK_WEIGHTS}'::float4[], d.tsv, websearch_to_tsquery('simple', ?)) AS rank
219
+ FROM search_docs d
220
+ WHERE ${where.join(' AND ')}
221
+ ORDER BY rank DESC, d.id DESC
222
+ LIMIT ?`,
223
+ params,
224
+ };
225
+ };
226
+
227
+ return {
228
+ async now(): Promise<string> {
229
+ // The INDEX's clock, in the index's own format — the same law the SQLite
230
+ // provider states. Comparing a JS `Date` against a value the database
231
+ // wrote is a format coupling nobody remembers is there.
232
+ const row = await pool.withConnection(async (connection) =>
233
+ createPostgresControlExecutor(connection).get<{ now: string }>({ sql: 'SELECT {{now}} AS now' }),
234
+ );
235
+ if (row === null)
236
+ throw new AppError('The search index clock returned no row.', {
237
+ code: 'SEARCH_CLOCK_UNAVAILABLE',
238
+ status: 500,
239
+ });
240
+ return row.now;
241
+ },
242
+
243
+ ...(options.embed ? { embed: options.embed } : {}),
244
+
245
+ async index(doc: SearchDoc): Promise<void> {
246
+ assertNonEmpty(doc.corpus, 'corpus');
247
+ assertNonEmpty(doc.id, 'id');
248
+ assertNonEmpty(doc.tenantId, 'tenantId');
249
+
250
+ // The document lands first and alone — the keyword tier is the floor, and
251
+ // an embedding round-trip is not something to hold a write lock across.
252
+ const identity = await scoped(doc.tenantId, async (executor) => {
253
+ const row = await executor.get<DocIdentity>({
254
+ sql: `INSERT INTO search_docs (corpus, source_id, tenant_id, user_id, title, body, rev, indexed_at)
255
+ VALUES (?, ?, ?, ?, ?, ?, 0, {{now}})
256
+ ON CONFLICT (corpus, source_id, tenant_id) DO UPDATE SET
257
+ user_id = excluded.user_id,
258
+ title = excluded.title,
259
+ body = excluded.body,
260
+ rev = search_docs.rev + 1,
261
+ indexed_at = excluded.indexed_at
262
+ RETURNING id, rev`,
263
+ params: [doc.corpus, doc.id, doc.tenantId, null, doc.title, doc.body],
264
+ });
265
+ if (row === null)
266
+ throw new AppError('The search index upsert returned no row, so the document id is unknown.', {
267
+ code: 'SEARCH_UPSERT_EMPTY',
268
+ status: 500,
269
+ context: { corpus: doc.corpus, id: doc.id },
270
+ });
271
+ return row;
272
+ });
273
+
274
+ if (!options.embed) return;
275
+ const embed = options.embed;
276
+ try {
277
+ await writeDocChunks(perStatementExecutor(doc.tenantId), {
278
+ // A pg `bigint` arrives as a string; it is an opaque key here either way.
279
+ docId: String(identity.id),
280
+ rev: Number(identity.rev),
281
+ text: doc.title ? `${doc.title}\n\n${doc.body}` : doc.body,
282
+ tenantId: doc.tenantId,
283
+ embed: (text, tenantId) => embed(text, tenantId),
284
+ model: embedModel,
285
+ ...(options.chunkOptions ? { chunkOptions: options.chunkOptions } : {}),
286
+ });
287
+ } catch (error) {
288
+ // Degrade, do not fail — see the SQLite provider's identical rule. The
289
+ // document IS indexed; failing here would make an embedder outage look
290
+ // like a write failure to the CDC tap, which retries.
291
+ options.onVectorError?.(error, 'index');
292
+ }
293
+ },
294
+
295
+ async remove(corpus: string, id: string, tenantId: string): Promise<void> {
296
+ // ONE transaction for both tables: an interrupted removal that dropped the
297
+ // document but kept its chunks would leave the vector tier ranking a
298
+ // document that no longer exists. (The FK cascade would handle it, but the
299
+ // explicit delete keeps the two profiles' behavior identical rather than
300
+ // resting on a schema detail one of them does not have.)
301
+ await scoped(tenantId, async (executor) => {
302
+ await executor.write({
303
+ sql: `DELETE FROM search_chunks WHERE doc_id IN (
304
+ SELECT id FROM search_docs WHERE corpus = ? AND source_id = ? AND tenant_id = ?)`,
305
+ params: [corpus, id, tenantId],
306
+ });
307
+ await executor.write({
308
+ sql: 'DELETE FROM search_docs WHERE corpus = ? AND source_id = ? AND tenant_id = ?',
309
+ params: [corpus, id, tenantId],
310
+ });
311
+ });
312
+ },
313
+
314
+ async query(q: string, opts: SearchQueryOptions): Promise<SearchHit[]> {
315
+ assertNonEmpty(opts.tenantId, 'tenantId');
316
+ const trimmed = q.trim();
317
+ if (trimmed.length === 0) return [];
318
+
319
+ const corpora = opts.corpus ? [...opts.corpus] : [];
320
+ const limit = clampLimit(opts.limit, defaultLimit, maxLimit);
321
+ const tierLimit = options.embed ? Math.min(maxLimit, limit * FUSION_DEPTH) : limit;
322
+
323
+ const rows = await scoped(opts.tenantId, (executor) =>
324
+ executor.all<HitRow>(keywordQuery(trimmed, opts.tenantId, corpora, tierLimit)),
325
+ );
326
+ const keywordHits: SearchHit[] = rows.map((row) => ({
327
+ id: row.id,
328
+ corpus: row.corpus,
329
+ tenantId: row.tenant_id,
330
+ // `ts_rank` is already higher-is-better, which is the interface's
331
+ // contract — so unlike BM25 there is no sign to flip. Said explicitly
332
+ // because "flip it, the other one does" is the mistake available here.
333
+ score: Number(row.rank),
334
+ title: row.title,
335
+ snippet: row.snippet ?? '',
336
+ }));
337
+
338
+ if (!options.embed) return keywordHits.slice(0, limit);
339
+
340
+ let vector: VectorRankResult;
341
+ try {
342
+ vector = await rankChunksByVector(perStatementExecutor(opts.tenantId), {
343
+ tenantId: opts.tenantId,
344
+ corpora,
345
+ queryVector: await options.embed(trimmed, opts.tenantId),
346
+ model: embedModel,
347
+ limit: tierLimit,
348
+ ...(options.vectorCandidateLimit !== undefined
349
+ ? { candidateLimit: options.vectorCandidateLimit }
350
+ : {}),
351
+ });
352
+ } catch (error) {
353
+ options.onVectorError?.(error, 'query');
354
+ return keywordHits.slice(0, limit);
355
+ }
356
+ if (vector.truncated)
357
+ options.onVectorTruncated?.({ tenantId: opts.tenantId, corpora, scanned: vector.scanned });
358
+
359
+ return await fuseSearchTiers({
360
+ keyword: keywordHits,
361
+ vector: vector.chunks,
362
+ limit,
363
+ tenantId: opts.tenantId,
364
+ hydrate: async (keys) => {
365
+ // ONE statement for every missing title. A per-document lookup here is
366
+ // an N+1 on the hot path, and each of those N is a pool checkout.
367
+ const placeholders = keys.map(() => '(?, ?)').join(', ');
368
+ const params = keys.flatMap((key) => [key.corpus, key.docId]);
369
+ const found = await scoped(opts.tenantId, (executor) =>
370
+ executor.all<DocMetaRow>({
371
+ sql: `SELECT corpus, source_id, title FROM search_docs
372
+ WHERE tenant_id = ? AND (corpus, source_id) IN (${placeholders})`,
373
+ params: [opts.tenantId, ...params],
374
+ }),
375
+ );
376
+ return new Map(found.map((row) => [docKey(row.corpus, row.source_id), { title: row.title }]));
377
+ },
378
+ });
379
+ },
380
+
381
+ async sweepStale(corpus: string, tenantId: string, epoch: string): Promise<number> {
382
+ assertNonEmpty(corpus, 'corpus');
383
+ assertNonEmpty(tenantId, 'tenantId');
384
+ assertNonEmpty(epoch, 'epoch');
385
+ return await scoped(tenantId, async (executor) => {
386
+ // Counted with its own statement rather than read off the DELETE's row
387
+ // count, so the number the reindex job logs is documents and not
388
+ // documents-plus-cascaded-chunks. The SQLite provider counts separately
389
+ // for the same reason (its FTS triggers inflate `changes`).
390
+ const stale = await executor.get<{ n: number | string }>({
391
+ sql: 'SELECT COUNT(*) AS n FROM search_docs WHERE corpus = ? AND tenant_id = ? AND indexed_at < ?',
392
+ params: [corpus, tenantId, epoch],
393
+ });
394
+ await executor.write({
395
+ sql: `DELETE FROM search_chunks WHERE doc_id IN (
396
+ SELECT id FROM search_docs WHERE corpus = ? AND tenant_id = ? AND indexed_at < ?)`,
397
+ params: [corpus, tenantId, epoch],
398
+ });
399
+ await executor.write({
400
+ sql: 'DELETE FROM search_docs WHERE corpus = ? AND tenant_id = ? AND indexed_at < ?',
401
+ params: [corpus, tenantId, epoch],
402
+ });
403
+ return Number(stale?.n ?? 0);
404
+ });
405
+ },
406
+
407
+ async count(corpus: string, tenantId: string): Promise<number> {
408
+ return await scoped(tenantId, async (executor) => {
409
+ const row = await executor.get<{ n: number | string }>({
410
+ sql: 'SELECT COUNT(*) AS n FROM search_docs WHERE corpus = ? AND tenant_id = ?',
411
+ params: [corpus, tenantId],
412
+ });
413
+ return Number(row?.n ?? 0);
414
+ });
415
+ },
416
+
417
+ async countChunks(corpus: string, tenantId: string): Promise<number> {
418
+ return await scoped(tenantId, (executor) => countChunks(executor, corpus, tenantId));
419
+ },
420
+ };
421
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * The saas search schema (04 §3) — `search_docs` + `search_chunks` on Postgres.
3
+ *
4
+ * `domains/search.ts` refuses to compile its SQLite chain for Postgres
5
+ * (`PG_DDL_UNSUPPORTED`) and says why: `search_fts` is an FTS5 external-content
6
+ * virtual table maintained by SQLite triggers, which is not a dialect
7
+ * difference — it is a different search engine. So the saas keyword tier is
8
+ * authored here, natively, and the two providers share only the vector tier
9
+ * (`cwip`'s `searchVectorTier`, one implementation for both).
10
+ *
11
+ * ## Why a GENERATED column rather than a trigger
12
+ *
13
+ * The SQLite side keeps its FTS shadow in step with three triggers, and its own
14
+ * header explains the hazard: "application-maintained shadow tables drift the
15
+ * moment one code path forgets". Postgres offers something stronger than a
16
+ * trigger for this — a `GENERATED ALWAYS AS (…) STORED` column cannot drift at
17
+ * all, because there is no code path that writes it. A trigger can be dropped, a
18
+ * migration can forget to recreate it, and `INSERT ... ON CONFLICT DO UPDATE`
19
+ * can bypass one that was written for the wrong event. A generated column has
20
+ * none of those failure modes: the tsvector is a function of the row, enforced
21
+ * by the storage engine.
22
+ *
23
+ * ## `simple` rather than `english`
24
+ *
25
+ * The text config is `simple` — no stemming, no stopwords. Two reasons, and the
26
+ * second is the load-bearing one:
27
+ *
28
+ * 1. This platform's corpora are multi-lingual by construction (a customer's
29
+ * notes, filenames, contact names). Stemming with the WRONG language is
30
+ * worse than not stemming: `english` drops "die"/"das" as if they were
31
+ * stopwords and stems "arbeiten" to nothing useful.
32
+ * 2. `english` discards stopwords, so a search for a title that IS a stopword
33
+ * phrase ("The Who", "It") returns nothing at all — and the SQLite side's
34
+ * `unicode61` tokenizer has no stopword list either. Two tiers that
35
+ * disagree about whether a document is findable is a bug report a customer
36
+ * files as "search works on my laptop".
37
+ *
38
+ * ## RLS
39
+ *
40
+ * `search_docs` is tenant-rooted (`SEARCH_TENANCY`), so it carries the same
41
+ * `tenant_isolation` policy every domain table does, and `search_chunks`
42
+ * inherits through its `doc_id` FK. That is defense in depth, not the primary
43
+ * control: the provider binds `tenant_id` into every statement it issues, the
44
+ * way the SQLite provider does. The policy is what makes a future statement that
45
+ * forgets return zero rows instead of everyone's.
46
+ *
47
+ * DDL is NEVER issued on a process start — {@link ensureSaasSearchSchema} is a
48
+ * named call a deployment makes once, for the same reason
49
+ * `ensureSaasControlPlaneSchema` is: a shared database has one schema owner, and
50
+ * a library that migrated on boot would be N of them racing.
51
+ */
52
+ import type { SaasPool, StatementExecutor } from 'cwip/storage';
53
+ import { assertRowLevelSecurity, createPostgresControlExecutor, rlsStatements } from 'cwip/storage';
54
+
55
+ /** The tsvector expression. Title weighted `A`, body `B` — `ts_rank` reads the weights. */
56
+ const TSV_EXPRESSION = `setweight(to_tsvector('simple', coalesce(title, '')), 'A') || setweight(to_tsvector('simple', coalesce(body, '')), 'B')`;
57
+
58
+ /**
59
+ * Weights fed to `ts_rank`, in Postgres' `{D, C, B, A}` order. Title 4× body,
60
+ * matching the SQLite provider's `bm25(search_fts, 4.0, 1.0)` exactly — two
61
+ * profiles that ranked the same corpus differently would make "it ranks wrong in
62
+ * production" unreproducible on a laptop.
63
+ */
64
+ export const PG_RANK_WEIGHTS = '{0.1, 0.1, 0.25, 1.0}';
65
+
66
+ export const PG_SEARCH_DOCS_DDL = `CREATE TABLE IF NOT EXISTS search_docs (
67
+ id BIGSERIAL PRIMARY KEY,
68
+ corpus TEXT NOT NULL,
69
+ source_id TEXT NOT NULL,
70
+ tenant_id TEXT NOT NULL,
71
+ user_id TEXT,
72
+ title TEXT NOT NULL DEFAULT '',
73
+ body TEXT NOT NULL DEFAULT '',
74
+ rev INTEGER NOT NULL DEFAULT 0,
75
+ indexed_at TEXT NOT NULL,
76
+ tsv tsvector GENERATED ALWAYS AS (${TSV_EXPRESSION}) STORED
77
+ )`;
78
+
79
+ export const PG_SEARCH_CHUNKS_DDL = `CREATE TABLE IF NOT EXISTS search_chunks (
80
+ id BIGSERIAL PRIMARY KEY,
81
+ doc_id BIGINT NOT NULL REFERENCES search_docs(id) ON DELETE CASCADE,
82
+ seq INTEGER NOT NULL,
83
+ text TEXT NOT NULL,
84
+ embedding BYTEA,
85
+ dims INTEGER,
86
+ model TEXT,
87
+ indexed_at TEXT NOT NULL
88
+ )`;
89
+
90
+ /**
91
+ * Every statement that builds the saas search schema, in dependency order.
92
+ *
93
+ * The unique index on `(corpus, source_id, tenant_id)` is what makes the
94
+ * provider's upsert idempotent — it is the same natural key the SQLite schema
95
+ * declares, and 05 §A1's cross-tenant-uniqueness lesson is why `tenant_id` is
96
+ * in it rather than assumed.
97
+ */
98
+ export const PG_SEARCH_DDL: readonly string[] = [
99
+ PG_SEARCH_DOCS_DDL,
100
+ 'CREATE UNIQUE INDEX IF NOT EXISTS search_docs_source ON search_docs(corpus, source_id, tenant_id)',
101
+ 'CREATE INDEX IF NOT EXISTS search_docs_tenant ON search_docs(tenant_id, corpus, indexed_at DESC)',
102
+ 'CREATE INDEX IF NOT EXISTS search_docs_tsv ON search_docs USING GIN(tsv)',
103
+ PG_SEARCH_CHUNKS_DDL,
104
+ 'CREATE UNIQUE INDEX IF NOT EXISTS search_chunks_seq ON search_chunks(doc_id, seq)',
105
+ // The vector tier's candidate scan filters on the join plus (dims, model);
106
+ // without this it is a sequential scan of every tenant's chunks per query.
107
+ 'CREATE INDEX IF NOT EXISTS search_chunks_space ON search_chunks(doc_id, dims, model)',
108
+ // The platform's OWN policy text, not a hand-written copy. `rlsStatements`
109
+ // also emits `FORCE ROW LEVEL SECURITY`, which is the half a hand-written
110
+ // chain forgets and the half that matters here: a table's OWNER bypasses RLS
111
+ // unless it is FORCEd, and in this platform the application role owns its own
112
+ // schema. Without it the policy is decoration for exactly the connection that
113
+ // serves every request.
114
+ ...rlsStatements('search_docs'),
115
+ ];
116
+
117
+ // Self-check at module load, the same one `toPostgresChain` performs on a
118
+ // compiled chain. This chain is hand-authored (SQLite's FTS5 shadow has no
119
+ // Postgres translation), so it does NOT go through the compiler that would
120
+ // normally apply this gate — and a hand-authored chain is precisely the one that
121
+ // can ship a tenant-rooted table with no policy.
122
+ assertRowLevelSecurity(PG_SEARCH_DDL);
123
+
124
+ /**
125
+ * Create the saas search schema. Idempotent, and called EXPLICITLY by a
126
+ * deployment — never on a process start. See the file header.
127
+ *
128
+ * `CREATE POLICY` has no `IF NOT EXISTS` in Postgres, so the policy statements
129
+ * are skipped when the policy is already there rather than being wrapped in a
130
+ * swallow-everything `DO` block: a block that ignores the duplicate-object error
131
+ * also ignores a typo in the predicate, which would leave the table enabled,
132
+ * forced, and isolated by a policy that never matches.
133
+ */
134
+ export const ensureSaasSearchSchema = async (pool: SaasPool): Promise<void> => {
135
+ await pool.withConnection(async (connection) => {
136
+ const executor: StatementExecutor = createPostgresControlExecutor(connection);
137
+ const existing = await executor.all<{ policyname: string }>({
138
+ sql: "SELECT policyname FROM pg_policies WHERE tablename = 'search_docs'",
139
+ });
140
+ const present = new Set(existing.map((row) => row.policyname));
141
+ for (const statement of PG_SEARCH_DDL) {
142
+ const policy = /^CREATE\s+POLICY\s+([a-z_]+)\s+ON\b/i.exec(statement.trim());
143
+ if (policy?.[1] !== undefined && present.has(policy[1])) continue;
144
+ await executor.write({ sql: statement });
145
+ }
146
+ });
147
+ };
@@ -0,0 +1,65 @@
1
+ /**
2
+ * `cursedbelt-server/sharing` — the fleet's ONE per-record sharing/access model.
3
+ *
4
+ * The vocabulary is `cursedbelt-core/sharing` (pure), the panel is
5
+ * `cursedbelt/react/sharing`, and this is the storage + resolver half. Read
6
+ * `./store.ts`'s header for the five properties it guarantees and `./sql.ts` for
7
+ * why the row filter is a SQL fragment rather than a list of ids.
8
+ */
9
+ export { SHARE_MIGRATIONS } from "./migrations";
10
+ export {
11
+ SHARE_EVENTS_TABLE,
12
+ SHARE_GRANTS_TABLE,
13
+ SHARE_GROUPS_TABLE,
14
+ SHARE_GROUP_MEMBERS_TABLE,
15
+ } from "./migrations";
16
+ export {
17
+ GROUP_SUBJECT_PREFIX,
18
+ mountShareRoutes,
19
+ parseSubjectParam,
20
+ subjectParam,
21
+ } from "./router";
22
+ export type { ShareRouterOptions, ShareRouterViewer, ShareRoutesPayload } from "./router";
23
+ export { sharedResourceSql, sqlColumn, sqlLiteral } from "./sql";
24
+ export type { SharedResourceSqlOptions } from "./sql";
25
+ export { createShareStore, group, user } from "./store";
26
+ export type {
27
+ GrantInput,
28
+ RevokeInput,
29
+ ShareEventQuery,
30
+ ShareStore,
31
+ ShareStoreOptions,
32
+ ShareTarget,
33
+ } from "./store";
34
+ // Re-exported so a server consumer needs ONE import for the whole contract; the
35
+ // pure entry (`cursedbelt-core/sharing`) stays available for a browser or a test.
36
+ export {
37
+ accessMeets,
38
+ describeAccess,
39
+ describeShareEvent,
40
+ isShareAccess,
41
+ isShareLevel,
42
+ isShareSubjectType,
43
+ levelsMeeting,
44
+ normalizeSubject,
45
+ normalizeSubjectId,
46
+ resolveAccess,
47
+ resourceKey,
48
+ sameResource,
49
+ sameSubject,
50
+ SHARE_ACTIONS,
51
+ strongerAccess,
52
+ } from "cursedbelt-core/sharing/model";
53
+ export type {
54
+ ShareAccess,
55
+ ShareAction,
56
+ ShareEvent,
57
+ ShareGrant,
58
+ ShareGroup,
59
+ ShareGroupMember,
60
+ ShareLevel,
61
+ ShareResource,
62
+ ShareSubject,
63
+ ShareSubjectType,
64
+ ShareViewer,
65
+ } from "cursedbelt-core/sharing/model";