@igstack/app-catalog-backend-core 2.0.1-alpha-20260228221600 → 2.0.1-alpha-20260915180957

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 (317) hide show
  1. package/dist/db/client.d.mts +79 -0
  2. package/dist/db/client.d.mts.map +1 -0
  3. package/dist/db/client.mjs +138 -0
  4. package/dist/db/client.mjs.map +1 -0
  5. package/dist/db/index.d.mts +5 -0
  6. package/dist/db/sslConfig.d.mts +28 -0
  7. package/dist/db/sslConfig.d.mts.map +1 -0
  8. package/dist/db/sslConfig.mjs +71 -0
  9. package/dist/db/sslConfig.mjs.map +1 -0
  10. package/dist/db/syncAppCatalog.d.mts +28 -0
  11. package/dist/db/syncAppCatalog.d.mts.map +1 -0
  12. package/dist/db/syncAppCatalog.mjs +242 -0
  13. package/dist/db/syncAppCatalog.mjs.map +1 -0
  14. package/dist/db/tableSyncMagazine.d.mts +44 -0
  15. package/dist/db/tableSyncMagazine.d.mts.map +1 -0
  16. package/dist/db/tableSyncMagazine.mjs +43 -0
  17. package/dist/db/tableSyncMagazine.mjs.map +1 -0
  18. package/dist/db/tableSyncPrismaAdapter.d.mts +27 -0
  19. package/dist/db/tableSyncPrismaAdapter.d.mts.map +1 -0
  20. package/dist/db/tableSyncPrismaAdapter.mjs +81 -0
  21. package/dist/db/tableSyncPrismaAdapter.mjs.map +1 -0
  22. package/dist/generated/prisma/client.d.mts +26 -0
  23. package/dist/generated/prisma/client.d.mts.map +1 -0
  24. package/dist/generated/prisma/client.mjs +26 -0
  25. package/dist/generated/prisma/client.mjs.map +1 -0
  26. package/dist/generated/prisma/commonInputTypes.d.mts +564 -0
  27. package/dist/generated/prisma/commonInputTypes.d.mts.map +1 -0
  28. package/dist/generated/prisma/enums.d.mts +17 -0
  29. package/dist/generated/prisma/enums.d.mts.map +1 -0
  30. package/dist/generated/prisma/internal/class.d.mts +291 -0
  31. package/dist/generated/prisma/internal/class.d.mts.map +1 -0
  32. package/dist/generated/prisma/internal/class.mjs +44 -0
  33. package/dist/generated/prisma/internal/class.mjs.map +1 -0
  34. package/dist/generated/prisma/internal/prismaNamespace.d.mts +1630 -0
  35. package/dist/generated/prisma/internal/prismaNamespace.d.mts.map +1 -0
  36. package/dist/generated/prisma/models/DbAppTagDefinition.d.mts +1007 -0
  37. package/dist/generated/prisma/models/DbAppTagDefinition.d.mts.map +1 -0
  38. package/dist/generated/prisma/models/DbApprovalMethod.d.mts +1024 -0
  39. package/dist/generated/prisma/models/DbApprovalMethod.d.mts.map +1 -0
  40. package/dist/generated/prisma/models/DbAsset.d.mts +1183 -0
  41. package/dist/generated/prisma/models/DbAsset.d.mts.map +1 -0
  42. package/dist/generated/prisma/models/DbComment.d.mts +1234 -0
  43. package/dist/generated/prisma/models/DbComment.d.mts.map +1 -0
  44. package/dist/generated/prisma/models/DbGroup.d.mts +1124 -0
  45. package/dist/generated/prisma/models/DbGroup.d.mts.map +1 -0
  46. package/dist/generated/prisma/models/DbGroupMembership.d.mts +1199 -0
  47. package/dist/generated/prisma/models/DbGroupMembership.d.mts.map +1 -0
  48. package/dist/generated/prisma/models/DbPerson.d.mts +1155 -0
  49. package/dist/generated/prisma/models/DbPerson.d.mts.map +1 -0
  50. package/dist/generated/prisma/models/DbResource.d.mts +2787 -0
  51. package/dist/generated/prisma/models/DbResource.d.mts.map +1 -0
  52. package/dist/generated/prisma/models/Source.d.mts +1031 -0
  53. package/dist/generated/prisma/models/Source.d.mts.map +1 -0
  54. package/dist/generated/prisma/models/SourceReference.d.mts +1357 -0
  55. package/dist/generated/prisma/models/SourceReference.d.mts.map +1 -0
  56. package/dist/generated/prisma/models/account.d.mts +1410 -0
  57. package/dist/generated/prisma/models/account.d.mts.map +1 -0
  58. package/dist/generated/prisma/models/session.d.mts +1232 -0
  59. package/dist/generated/prisma/models/session.d.mts.map +1 -0
  60. package/dist/generated/prisma/models/user.d.mts +1343 -0
  61. package/dist/generated/prisma/models/user.d.mts.map +1 -0
  62. package/dist/generated/prisma/models/verification.d.mts +988 -0
  63. package/dist/generated/prisma/models/verification.d.mts.map +1 -0
  64. package/dist/generated/prisma/models.d.mts +15 -0
  65. package/dist/index.d.mts +41 -0
  66. package/dist/index.mjs +28 -0
  67. package/dist/middleware/backendResolver.mjs +26 -0
  68. package/dist/middleware/backendResolver.mjs.map +1 -0
  69. package/dist/middleware/createAcMiddleware.d.mts +7 -0
  70. package/dist/middleware/createAcMiddleware.d.mts.map +1 -0
  71. package/dist/middleware/createAcMiddleware.mjs +90 -0
  72. package/dist/middleware/createAcMiddleware.mjs.map +1 -0
  73. package/dist/middleware/database.d.mts +25 -0
  74. package/dist/middleware/database.d.mts.map +1 -0
  75. package/dist/middleware/database.mjs +60 -0
  76. package/dist/middleware/database.mjs.map +1 -0
  77. package/dist/middleware/featureRegistry.mjs +81 -0
  78. package/dist/middleware/featureRegistry.mjs.map +1 -0
  79. package/dist/middleware/htmlInjection.d.mts +15 -0
  80. package/dist/middleware/htmlInjection.d.mts.map +1 -0
  81. package/dist/middleware/htmlInjection.mjs +20 -0
  82. package/dist/middleware/htmlInjection.mjs.map +1 -0
  83. package/dist/middleware/index.d.mts +4 -0
  84. package/dist/middleware/types.d.mts +204 -0
  85. package/dist/middleware/types.d.mts.map +1 -0
  86. package/dist/modules/appCatalog/checkLinks.d.mts +37 -0
  87. package/dist/modules/appCatalog/checkLinks.d.mts.map +1 -0
  88. package/dist/modules/appCatalog/checkLinks.mjs +230 -0
  89. package/dist/modules/appCatalog/checkLinks.mjs.map +1 -0
  90. package/dist/modules/appCatalog/freshness.mjs +31 -0
  91. package/dist/modules/appCatalog/freshness.mjs.map +1 -0
  92. package/dist/modules/appCatalog/service.d.mts +7 -0
  93. package/dist/modules/appCatalog/service.d.mts.map +1 -0
  94. package/dist/modules/appCatalog/service.mjs +214 -0
  95. package/dist/modules/appCatalog/service.mjs.map +1 -0
  96. package/dist/modules/assets/assetCache.mjs +25 -0
  97. package/dist/modules/assets/assetCache.mjs.map +1 -0
  98. package/dist/modules/assets/assetRestController.d.mts +22 -0
  99. package/dist/modules/assets/assetRestController.d.mts.map +1 -0
  100. package/dist/modules/assets/assetRestController.mjs +187 -0
  101. package/dist/modules/assets/assetRestController.mjs.map +1 -0
  102. package/dist/modules/assets/assetUtils.mjs +88 -0
  103. package/dist/modules/assets/assetUtils.mjs.map +1 -0
  104. package/dist/modules/assets/screenshotRestController.d.mts +21 -0
  105. package/dist/modules/assets/screenshotRestController.d.mts.map +1 -0
  106. package/dist/modules/assets/screenshotRestController.mjs +153 -0
  107. package/dist/modules/assets/screenshotRestController.mjs.map +1 -0
  108. package/dist/modules/assets/syncAssets.d.mts +28 -0
  109. package/dist/modules/assets/syncAssets.d.mts.map +1 -0
  110. package/dist/modules/assets/syncAssets.mjs +178 -0
  111. package/dist/modules/assets/syncAssets.mjs.map +1 -0
  112. package/dist/modules/assets/upsertAsset.mjs +42 -0
  113. package/dist/modules/assets/upsertAsset.mjs.map +1 -0
  114. package/dist/modules/auth/auth.d.mts +8 -0
  115. package/dist/modules/auth/auth.d.mts.map +1 -0
  116. package/dist/modules/auth/auth.mjs +17 -0
  117. package/dist/modules/auth/auth.mjs.map +1 -0
  118. package/dist/modules/auth/authRouter.d.mts +50 -0
  119. package/dist/modules/auth/authRouter.d.mts.map +1 -0
  120. package/dist/modules/auth/authRouter.mjs +44 -0
  121. package/dist/modules/auth/authRouter.mjs.map +1 -0
  122. package/dist/modules/auth/devMockUserUtils.mjs +42 -0
  123. package/dist/modules/auth/devMockUserUtils.mjs.map +1 -0
  124. package/dist/modules/auth/registerAuthRoutes.d.mts +18 -0
  125. package/dist/modules/auth/registerAuthRoutes.d.mts.map +1 -0
  126. package/dist/modules/auth/registerAuthRoutes.mjs +44 -0
  127. package/dist/modules/auth/registerAuthRoutes.mjs.map +1 -0
  128. package/dist/modules/comments/commentsRouter.d.mts +47 -0
  129. package/dist/modules/comments/commentsRouter.d.mts.map +1 -0
  130. package/dist/modules/comments/commentsRouter.mjs +55 -0
  131. package/dist/modules/comments/commentsRouter.mjs.map +1 -0
  132. package/dist/modules/comments/service.d.mts +25 -0
  133. package/dist/modules/comments/service.d.mts.map +1 -0
  134. package/dist/modules/comments/service.mjs +86 -0
  135. package/dist/modules/comments/service.mjs.map +1 -0
  136. package/dist/modules/comments/visitorIdentity.d.mts +15 -0
  137. package/dist/modules/comments/visitorIdentity.d.mts.map +1 -0
  138. package/dist/modules/comments/visitorIdentity.mjs +121 -0
  139. package/dist/modules/comments/visitorIdentity.mjs.map +1 -0
  140. package/dist/modules/icons/iconRestController.d.mts +21 -0
  141. package/dist/modules/icons/iconRestController.d.mts.map +1 -0
  142. package/dist/modules/icons/iconRestController.mjs +126 -0
  143. package/dist/modules/icons/iconRestController.mjs.map +1 -0
  144. package/dist/modules/icons/iconService.d.mts +57 -0
  145. package/dist/modules/icons/iconService.d.mts.map +1 -0
  146. package/dist/modules/icons/iconService.mjs +65 -0
  147. package/dist/modules/icons/iconService.mjs.map +1 -0
  148. package/dist/modules/icons/iconUtils.mjs +28 -0
  149. package/dist/modules/icons/iconUtils.mjs.map +1 -0
  150. package/dist/modules/lighthouseKeeper/demo.d.mts +7 -0
  151. package/dist/modules/lighthouseKeeper/demo.d.mts.map +1 -0
  152. package/dist/modules/lighthouseKeeper/demo.mjs +67 -0
  153. package/dist/modules/lighthouseKeeper/demo.mjs.map +1 -0
  154. package/dist/modules/lighthouseKeeper/tools.d.mts +17 -0
  155. package/dist/modules/lighthouseKeeper/tools.d.mts.map +1 -0
  156. package/dist/modules/lighthouseKeeper/tools.mjs +82 -0
  157. package/dist/modules/lighthouseKeeper/tools.mjs.map +1 -0
  158. package/dist/server/acStaticControllerContract.d.mts +17 -0
  159. package/dist/server/acStaticControllerContract.d.mts.map +1 -0
  160. package/dist/server/acStaticControllerContract.mjs +15 -0
  161. package/dist/server/acStaticControllerContract.mjs.map +1 -0
  162. package/dist/server/acTrpcContext.d.mts +31 -0
  163. package/dist/server/acTrpcContext.d.mts.map +1 -0
  164. package/dist/server/acTrpcContext.mjs +13 -0
  165. package/dist/server/acTrpcContext.mjs.map +1 -0
  166. package/dist/server/controller.d.mts +131 -0
  167. package/dist/server/controller.d.mts.map +1 -0
  168. package/dist/server/controller.mjs +52 -0
  169. package/dist/server/controller.mjs.map +1 -0
  170. package/dist/server/trpcSetup.mjs +60 -0
  171. package/dist/server/trpcSetup.mjs.map +1 -0
  172. package/dist/types/backend/api.d.mts +60 -0
  173. package/dist/types/backend/api.d.mts.map +1 -0
  174. package/dist/types/backend/common.d.mts +13 -0
  175. package/dist/types/backend/common.d.mts.map +1 -0
  176. package/dist/types/backend/companySpecificBackend.d.mts +14 -0
  177. package/dist/types/backend/companySpecificBackend.d.mts.map +1 -0
  178. package/dist/types/backend/dataSources.d.mts +25 -0
  179. package/dist/types/backend/dataSources.d.mts.map +1 -0
  180. package/dist/types/backend/deployments.d.mts +39 -0
  181. package/dist/types/backend/deployments.d.mts.map +1 -0
  182. package/dist/types/common/app/appTypes.d.mts +17 -0
  183. package/dist/types/common/app/appTypes.d.mts.map +1 -0
  184. package/dist/types/common/app/ui/appUiTypes.d.mts +15 -0
  185. package/dist/types/common/app/ui/appUiTypes.d.mts.map +1 -0
  186. package/dist/types/common/appCatalogTypes.d.mts +158 -0
  187. package/dist/types/common/appCatalogTypes.d.mts.map +1 -0
  188. package/dist/types/common/approvalMethodTypes.d.mts +127 -0
  189. package/dist/types/common/approvalMethodTypes.d.mts.map +1 -0
  190. package/dist/types/common/env/envTypes.d.mts +11 -0
  191. package/dist/types/common/env/envTypes.d.mts.map +1 -0
  192. package/dist/types/common/personGroupTypes.d.mts +28 -0
  193. package/dist/types/common/personGroupTypes.d.mts.map +1 -0
  194. package/dist/types/common/resourceTypes.d.mts +12 -0
  195. package/dist/types/common/resourceTypes.d.mts.map +1 -0
  196. package/dist/types/common/sharedTypes.d.mts +8 -0
  197. package/dist/types/common/sharedTypes.d.mts.map +1 -0
  198. package/dist/utils/cookies.mjs +12 -0
  199. package/dist/utils/cookies.mjs.map +1 -0
  200. package/dist/utils/naturalSort.mjs +23 -0
  201. package/dist/utils/naturalSort.mjs.map +1 -0
  202. package/dist/utils/parseSourceSlug.mjs +12 -0
  203. package/dist/utils/parseSourceSlug.mjs.map +1 -0
  204. package/dist/utils/versionUtils.d.mts +25 -0
  205. package/dist/utils/versionUtils.d.mts.map +1 -0
  206. package/dist/utils/versionUtils.mjs +97 -0
  207. package/dist/utils/versionUtils.mjs.map +1 -0
  208. package/package.json +29 -23
  209. package/prisma/migrations/20260813000000_add_ai_memory/migration.sql +2 -0
  210. package/prisma/migrations/20260914000000_add_comments/migration.sql +22 -0
  211. package/prisma/schema.prisma +138 -25
  212. package/prisma.config.ts +13 -0
  213. package/src/__tests__/assetMime.test.ts +164 -0
  214. package/src/__tests__/comments.test.ts +73 -0
  215. package/src/__tests__/dbSchema.test.ts +129 -0
  216. package/src/__tests__/dbSchemaAdapter.test.ts +118 -0
  217. package/src/__tests__/freshness.test.ts +102 -0
  218. package/src/__tests__/htmlInjection.test.ts +61 -0
  219. package/src/__tests__/iconCache.test.ts +81 -0
  220. package/src/__tests__/sslConfig.test.ts +90 -0
  221. package/src/__tests__/utils/naturalSort.test.ts +20 -0
  222. package/src/__tests__/utils/versionUtils.test.ts +30 -0
  223. package/src/db/client.ts +135 -2
  224. package/src/db/index.ts +11 -1
  225. package/src/db/sslConfig.ts +94 -0
  226. package/src/db/syncAppCatalog.ts +230 -104
  227. package/src/db/tableSyncMagazine.ts +28 -5
  228. package/src/db/tableSyncPrismaAdapter.ts +13 -14
  229. package/src/generated/prisma/browser.ts +94 -0
  230. package/src/generated/prisma/client.ts +118 -0
  231. package/src/generated/prisma/commonInputTypes.ts +658 -0
  232. package/src/generated/prisma/enums.ts +28 -0
  233. package/src/generated/prisma/internal/class.ts +334 -0
  234. package/src/generated/prisma/internal/prismaNamespace.ts +2046 -0
  235. package/src/generated/prisma/internal/prismaNamespaceBrowser.ts +340 -0
  236. package/src/generated/prisma/models/DbAppTagDefinition.ts +1199 -0
  237. package/src/generated/prisma/models/DbApprovalMethod.ts +1220 -0
  238. package/src/generated/prisma/models/DbAsset.ts +1386 -0
  239. package/src/generated/prisma/models/DbComment.ts +1452 -0
  240. package/src/generated/prisma/models/DbGroup.ts +1324 -0
  241. package/src/generated/prisma/models/DbGroupMembership.ts +1432 -0
  242. package/src/generated/prisma/models/DbPerson.ts +1356 -0
  243. package/src/generated/prisma/models/DbResource.ts +3068 -0
  244. package/src/generated/prisma/models/Source.ts +1228 -0
  245. package/src/generated/prisma/models/SourceReference.ts +1590 -0
  246. package/src/generated/prisma/models/account.ts +1632 -0
  247. package/src/generated/prisma/models/session.ts +1447 -0
  248. package/src/generated/prisma/models/user.ts +1562 -0
  249. package/src/generated/prisma/models/verification.ts +1180 -0
  250. package/src/generated/prisma/models.ts +25 -0
  251. package/src/generated/prisma/pjtg.ts +183 -0
  252. package/src/index.ts +54 -54
  253. package/src/middleware/backendResolver.ts +12 -11
  254. package/src/middleware/{createEhMiddleware.ts → createAcMiddleware.ts} +36 -58
  255. package/src/middleware/database.ts +30 -11
  256. package/src/middleware/featureRegistry.ts +55 -83
  257. package/src/middleware/htmlInjection.ts +25 -0
  258. package/src/middleware/index.ts +14 -11
  259. package/src/middleware/types.ts +61 -43
  260. package/src/modules/admin/chat/createAdminChatHandler.ts +3 -5
  261. package/src/modules/admin/chat/createDatabaseTools.ts +14 -10
  262. package/src/modules/appCatalog/checkLinks.ts +393 -0
  263. package/src/modules/appCatalog/freshness.ts +65 -0
  264. package/src/modules/appCatalog/service.ts +297 -48
  265. package/src/modules/assets/assetCache.ts +30 -0
  266. package/src/modules/assets/assetRestController.ts +25 -2
  267. package/src/modules/assets/assetUtils.ts +1 -0
  268. package/src/modules/assets/screenshotRestController.ts +136 -108
  269. package/src/modules/assets/screenshotRouter.ts +8 -4
  270. package/src/modules/assets/syncAssets.ts +5 -8
  271. package/src/modules/assets/upsertAsset.ts +26 -2
  272. package/src/modules/auth/auth.ts +7 -42
  273. package/src/modules/auth/authRouter.ts +8 -7
  274. package/src/modules/auth/devMockUserUtils.ts +4 -12
  275. package/src/modules/auth/registerAuthRoutes.ts +27 -5
  276. package/src/modules/comments/commentsRouter.ts +85 -0
  277. package/src/modules/comments/service.ts +154 -0
  278. package/src/modules/comments/visitorIdentity.ts +146 -0
  279. package/src/modules/icons/iconRestController.ts +11 -1
  280. package/src/modules/icons/iconService.ts +5 -5
  281. package/src/modules/lighthouseKeeper/demo.ts +116 -0
  282. package/src/modules/lighthouseKeeper/tools.ts +118 -0
  283. package/src/prisma-json-types.d.ts +11 -5
  284. package/src/server/{ehStaticControllerContract.ts → acStaticControllerContract.ts} +3 -3
  285. package/src/server/acTrpcContext.ts +31 -0
  286. package/src/server/controller.ts +58 -28
  287. package/src/server/trpcSetup.ts +5 -21
  288. package/src/types/backend/api.ts +33 -33
  289. package/src/types/backend/common.ts +3 -3
  290. package/src/types/backend/companySpecificBackend.ts +10 -2
  291. package/src/types/backend/dataSources.ts +9 -9
  292. package/src/types/backend/deployments.ts +11 -11
  293. package/src/types/common/app/appTypes.ts +7 -7
  294. package/src/types/common/app/ui/appUiTypes.ts +4 -4
  295. package/src/types/common/appCatalogTypes.ts +134 -10
  296. package/src/types/common/approvalMethodTypes.ts +68 -56
  297. package/src/types/common/env/envTypes.ts +3 -3
  298. package/src/types/common/personGroupTypes.ts +27 -0
  299. package/src/types/common/resourceTypes.ts +2 -2
  300. package/src/types/common/sharedTypes.ts +1 -1
  301. package/src/types/index.ts +1 -0
  302. package/src/utils/cookies.ts +10 -0
  303. package/src/utils/naturalSort.ts +18 -0
  304. package/src/utils/parseSourceSlug.ts +8 -0
  305. package/src/utils/versionUtils.ts +129 -0
  306. package/dist/index.d.ts +0 -1934
  307. package/dist/index.d.ts.map +0 -1
  308. package/dist/index.js +0 -2539
  309. package/dist/index.js.map +0 -1
  310. package/src/modules/appCatalogAdmin/appCatalogAdminRouter.ts +0 -187
  311. package/src/modules/appCatalogAdmin/catalogBackupController.ts +0 -213
  312. package/src/modules/approvalMethod/approvalMethodRouter.ts +0 -169
  313. package/src/modules/approvalMethod/slugUtils.ts +0 -17
  314. package/src/modules/approvalMethod/syncApprovalMethods.ts +0 -38
  315. package/src/modules/auth/authProviders.ts +0 -40
  316. package/src/modules/auth/authorizationUtils.ts +0 -132
  317. package/src/server/ehTrpcContext.ts +0 -26
@@ -0,0 +1,79 @@
1
+ import { PrismaClient } from "../generated/prisma/client.mjs";
2
+ import pg from "pg";
3
+
4
+ //#region src/db/client.d.ts
5
+ type PrismaLogOption = NonNullable<ConstructorParameters<typeof PrismaClient>[0]>['log'];
6
+ /**
7
+ * The schema a deployment should be reading and writing.
8
+ *
9
+ * DB_SCHEMA is set per deployment, so it outranks a schema baked into the app
10
+ * config - otherwise a config that names `public` would pin a preview env back
11
+ * to the shared tables it was meant to be isolated from.
12
+ */
13
+ declare function resolveDbSchema(configuredSchema?: string): string | undefined;
14
+ /**
15
+ * `search_path` startup options for a schema-isolated deployment.
16
+ *
17
+ * node-postgres ignores Prisma's `?schema=` URL parameter, so a preview env's
18
+ * schema has to be injected as a connection option instead. This half covers
19
+ * raw SQL and the migrations; Prisma's own queries need the adapter half too,
20
+ * which is why every core connection goes through createCorePrismaClient.
21
+ */
22
+ declare function buildPgSchemaOptions(configuredSchema?: string): {
23
+ options?: string;
24
+ };
25
+ /**
26
+ * The one way to open a connection to the core database.
27
+ *
28
+ * Schema isolation takes two halves and both live here:
29
+ * - the pool's `search_path`, which raw SQL and the migrations follow;
30
+ * - the adapter's `schema`, which is what Prisma qualifies its table names
31
+ * with. A client built without it emits `"public"."DbResource"` however
32
+ * correct the pool's `current_schema()` is, so a preview env silently reads
33
+ * and overwrites the shared canonical tables.
34
+ *
35
+ * A pool built outside this factory gets neither half, so don't build one.
36
+ */
37
+ declare function createCorePrismaClient(params: {
38
+ connectionString: string;
39
+ configuredSchema?: string;
40
+ log?: PrismaLogOption;
41
+ }): {
42
+ client: PrismaClient;
43
+ pool: pg.Pool;
44
+ schema: string | undefined;
45
+ };
46
+ /**
47
+ * Fails a deployment that believes it is schema-isolated but is not.
48
+ *
49
+ * Speaks up whenever a schema was resolved at all - the same resolution feeds the
50
+ * pool's `search_path`, so the expectation and the connection can never disagree
51
+ * by construction, and a deployment that configures isolation without setting
52
+ * DB_SCHEMA is checked too. The case worth catching is a missing schema: Postgres
53
+ * silently skips a `search_path` entry that does not exist and falls through to
54
+ * `public`, which is the shared-catalog corruption this is here to prevent.
55
+ */
56
+ declare function verifyDbSchema(poolToCheck: pg.Pool, configuredSchema?: string): Promise<void>;
57
+ /**
58
+ * Gets the internal Prisma client instance.
59
+ * Creates one if it doesn't exist.
60
+ */
61
+ declare function getDbClient(): PrismaClient;
62
+ /**
63
+ * Sets the internal Prisma client instance.
64
+ * Used by middleware to bridge with existing getDbClient() usage.
65
+ */
66
+ declare function setDbClient(client: PrismaClient): void;
67
+ /**
68
+ * Connects to the database.
69
+ * Call this before performing database operations.
70
+ */
71
+ declare function connectDb(): Promise<void>;
72
+ /**
73
+ * Disconnects from the database.
74
+ * Call this when done with database operations (e.g., in scripts).
75
+ */
76
+ declare function disconnectDb(): Promise<void>;
77
+ //#endregion
78
+ export { buildPgSchemaOptions, connectDb, createCorePrismaClient, disconnectDb, getDbClient, resolveDbSchema, setDbClient, verifyDbSchema };
79
+ //# sourceMappingURL=client.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.mts","names":[],"sources":["../../src/db/client.ts"],"mappings":";;;;KAQK,eAAA,GAAkB,WAAA,CACrB,qBAAA,QAA6B,YAAA;;AAPZ;;;;;;iBAiBH,eAAA,CAAgB,gBAAA;;;;;;;AAAhC;;iBAYgB,oBAAA,CAAqB,gBAAA;EACnC,OAAA;AAAA;AADF;;;;;AAmBA;;;;;;;AAnBA,iBAmBgB,sBAAA,CAAuB,MAAA;EACrC,gBAAA;EACA,gBAAA;EACA,GAAA,GAAM,eAAA;AAAA;EACF,MAAA,EAAQ,YAAA;EAAc,IAAA,EAAM,EAAA,CAAG,IAAA;EAAM,MAAA;AAAA;;;;;;AA8C3C;;;;;iBAAsB,cAAA,CACpB,WAAA,EAAa,EAAA,CAAG,IAAA,EAChB,gBAAA,YACC,OAAA;;;;;iBAuBa,WAAA,CAAA,GAAe,YAAA;AAA/B;;;;AAAA,iBAqBgB,WAAA,CAAY,MAAA,EAAQ,YAAA;AAApC;;;;AAAA,iBAQsB,SAAA,CAAA,GAAa,OAAA;AAAnC;;;;AAAA,iBAUsB,YAAA,CAAA,GAAgB,OAAA"}
@@ -0,0 +1,138 @@
1
+ import { PrismaClient } from "../generated/prisma/client.mjs";
2
+ import { buildPgSslConfig } from "./sslConfig.mjs";
3
+ import { PrismaPg } from "@prisma/adapter-pg";
4
+ import pg from "pg";
5
+
6
+ //#region src/db/client.ts
7
+ let prismaClient = null;
8
+ let pool = null;
9
+ /**
10
+ * The schema a deployment should be reading and writing.
11
+ *
12
+ * DB_SCHEMA is set per deployment, so it outranks a schema baked into the app
13
+ * config - otherwise a config that names `public` would pin a preview env back
14
+ * to the shared tables it was meant to be isolated from.
15
+ */
16
+ function resolveDbSchema(configuredSchema) {
17
+ return process.env.DB_SCHEMA || configuredSchema || void 0;
18
+ }
19
+ /**
20
+ * `search_path` startup options for a schema-isolated deployment.
21
+ *
22
+ * node-postgres ignores Prisma's `?schema=` URL parameter, so a preview env's
23
+ * schema has to be injected as a connection option instead. This half covers
24
+ * raw SQL and the migrations; Prisma's own queries need the adapter half too,
25
+ * which is why every core connection goes through createCorePrismaClient.
26
+ */
27
+ function buildPgSchemaOptions(configuredSchema) {
28
+ const schema = resolveDbSchema(configuredSchema);
29
+ return schema ? { options: `-c search_path=${schema}` } : {};
30
+ }
31
+ /**
32
+ * The one way to open a connection to the core database.
33
+ *
34
+ * Schema isolation takes two halves and both live here:
35
+ * - the pool's `search_path`, which raw SQL and the migrations follow;
36
+ * - the adapter's `schema`, which is what Prisma qualifies its table names
37
+ * with. A client built without it emits `"public"."DbResource"` however
38
+ * correct the pool's `current_schema()` is, so a preview env silently reads
39
+ * and overwrites the shared canonical tables.
40
+ *
41
+ * A pool built outside this factory gets neither half, so don't build one.
42
+ */
43
+ function createCorePrismaClient(params) {
44
+ const schema = resolveDbSchema(params.configuredSchema);
45
+ const ssl = buildPgSslConfig();
46
+ const newPool = new pg.Pool({
47
+ connectionString: params.connectionString,
48
+ ...ssl === void 0 ? {} : { ssl },
49
+ ...buildPgSchemaOptions(params.configuredSchema)
50
+ });
51
+ return {
52
+ client: new PrismaClient({
53
+ adapter: new PrismaPg(newPool, { schema }),
54
+ ...params.log === void 0 ? {} : { log: params.log }
55
+ }),
56
+ pool: newPool,
57
+ schema
58
+ };
59
+ }
60
+ /** Postgres NAMEDATALEN - 1: identifiers are truncated to this many bytes. */
61
+ const PG_MAX_IDENTIFIER_BYTES = 63;
62
+ /**
63
+ * How Postgres will have stored an identifier this long.
64
+ *
65
+ * A schema name over the limit is truncated identically wherever it appears - at
66
+ * CREATE SCHEMA and in `search_path` alike - so the isolation still holds and
67
+ * current_schema() merely reports the shortened name. Comparing raw strings would
68
+ * fail a correctly isolated deployment, so compare what Postgres kept.
69
+ */
70
+ function truncateIdentifier(name) {
71
+ const bytes = Buffer.from(name, "utf8");
72
+ if (bytes.length <= PG_MAX_IDENTIFIER_BYTES) return name;
73
+ return bytes.subarray(0, PG_MAX_IDENTIFIER_BYTES).toString("utf8");
74
+ }
75
+ /**
76
+ * Fails a deployment that believes it is schema-isolated but is not.
77
+ *
78
+ * Speaks up whenever a schema was resolved at all - the same resolution feeds the
79
+ * pool's `search_path`, so the expectation and the connection can never disagree
80
+ * by construction, and a deployment that configures isolation without setting
81
+ * DB_SCHEMA is checked too. The case worth catching is a missing schema: Postgres
82
+ * silently skips a `search_path` entry that does not exist and falls through to
83
+ * `public`, which is the shared-catalog corruption this is here to prevent.
84
+ */
85
+ async function verifyDbSchema(poolToCheck, configuredSchema) {
86
+ const resolved = resolveDbSchema(configuredSchema);
87
+ const actual = (await poolToCheck.query("SELECT current_schema() AS schema")).rows[0]?.schema ?? null;
88
+ console.log(`[db] core schema: resolved=${resolved ?? "(none)"} current_schema=${actual ?? "(none)"}`);
89
+ if (!resolved) return;
90
+ if (actual !== truncateIdentifier(resolved)) throw new Error(`The configured schema is "${resolved}" but the database resolved current_schema() to "${actual}". The schema is most likely missing, so every query would fall through to the shared catalog tables. Migrate this schema before starting the app.`);
91
+ }
92
+ /**
93
+ * Gets the internal Prisma client instance.
94
+ * Creates one if it doesn't exist.
95
+ */
96
+ function getDbClient() {
97
+ if (!prismaClient) {
98
+ const databaseUrl = process.env.AC_CORE_DATABASE_URL;
99
+ if (!databaseUrl) throw new Error("PrismaClient not initialized. You must call createAcMiddleware() before using database functions, or set AC_CORE_DATABASE_URL environment variable for standalone usage.");
100
+ const created = createCorePrismaClient({ connectionString: databaseUrl });
101
+ pool = created.pool;
102
+ prismaClient = created.client;
103
+ }
104
+ return prismaClient;
105
+ }
106
+ /**
107
+ * Sets the internal Prisma client instance.
108
+ * Used by middleware to bridge with existing getDbClient() usage.
109
+ */
110
+ function setDbClient(client) {
111
+ prismaClient = client;
112
+ }
113
+ /**
114
+ * Connects to the database.
115
+ * Call this before performing database operations.
116
+ */
117
+ async function connectDb() {
118
+ await getDbClient().$connect();
119
+ if (pool) await verifyDbSchema(pool);
120
+ }
121
+ /**
122
+ * Disconnects from the database.
123
+ * Call this when done with database operations (e.g., in scripts).
124
+ */
125
+ async function disconnectDb() {
126
+ if (prismaClient) {
127
+ await prismaClient.$disconnect();
128
+ prismaClient = null;
129
+ }
130
+ if (pool) {
131
+ await pool.end();
132
+ pool = null;
133
+ }
134
+ }
135
+
136
+ //#endregion
137
+ export { buildPgSchemaOptions, connectDb, createCorePrismaClient, disconnectDb, getDbClient, resolveDbSchema, setDbClient, verifyDbSchema };
138
+ //# sourceMappingURL=client.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.mjs","names":[],"sources":["../../src/db/client.ts"],"sourcesContent":["import { PrismaClient } from '../generated/prisma/client'\nimport { PrismaPg } from '@prisma/adapter-pg'\nimport pg from 'pg'\nimport { buildPgSslConfig } from './sslConfig'\n\nlet prismaClient: PrismaClient | null = null\nlet pool: pg.Pool | null = null\n\ntype PrismaLogOption = NonNullable<\n ConstructorParameters<typeof PrismaClient>[0]\n>['log']\n\n/**\n * The schema a deployment should be reading and writing.\n *\n * DB_SCHEMA is set per deployment, so it outranks a schema baked into the app\n * config - otherwise a config that names `public` would pin a preview env back\n * to the shared tables it was meant to be isolated from.\n */\nexport function resolveDbSchema(configuredSchema?: string): string | undefined {\n return process.env.DB_SCHEMA || configuredSchema || undefined\n}\n\n/**\n * `search_path` startup options for a schema-isolated deployment.\n *\n * node-postgres ignores Prisma's `?schema=` URL parameter, so a preview env's\n * schema has to be injected as a connection option instead. This half covers\n * raw SQL and the migrations; Prisma's own queries need the adapter half too,\n * which is why every core connection goes through createCorePrismaClient.\n */\nexport function buildPgSchemaOptions(configuredSchema?: string): {\n options?: string\n} {\n const schema = resolveDbSchema(configuredSchema)\n return schema ? { options: `-c search_path=${schema}` } : {}\n}\n\n/**\n * The one way to open a connection to the core database.\n *\n * Schema isolation takes two halves and both live here:\n * - the pool's `search_path`, which raw SQL and the migrations follow;\n * - the adapter's `schema`, which is what Prisma qualifies its table names\n * with. A client built without it emits `\"public\".\"DbResource\"` however\n * correct the pool's `current_schema()` is, so a preview env silently reads\n * and overwrites the shared canonical tables.\n *\n * A pool built outside this factory gets neither half, so don't build one.\n */\nexport function createCorePrismaClient(params: {\n connectionString: string\n configuredSchema?: string\n log?: PrismaLogOption\n}): { client: PrismaClient; pool: pg.Pool; schema: string | undefined } {\n const schema = resolveDbSchema(params.configuredSchema)\n // SSL comes from PGSSLMODE/PGSSLROOTCERT via our helper (node-postgres\n // doesn't honor those correctly for a connection-string pool - see\n // buildPgSslConfig).\n const ssl = buildPgSslConfig()\n const newPool = new pg.Pool({\n connectionString: params.connectionString,\n ...(ssl === undefined ? {} : { ssl }),\n ...buildPgSchemaOptions(params.configuredSchema),\n })\n const adapter = new PrismaPg(newPool, { schema })\n const client = new PrismaClient({\n adapter,\n ...(params.log === undefined ? {} : { log: params.log }),\n })\n return { client, pool: newPool, schema }\n}\n\n/** Postgres NAMEDATALEN - 1: identifiers are truncated to this many bytes. */\nconst PG_MAX_IDENTIFIER_BYTES = 63\n\n/**\n * How Postgres will have stored an identifier this long.\n *\n * A schema name over the limit is truncated identically wherever it appears - at\n * CREATE SCHEMA and in `search_path` alike - so the isolation still holds and\n * current_schema() merely reports the shortened name. Comparing raw strings would\n * fail a correctly isolated deployment, so compare what Postgres kept.\n */\nfunction truncateIdentifier(name: string): string {\n const bytes = Buffer.from(name, 'utf8')\n if (bytes.length <= PG_MAX_IDENTIFIER_BYTES) return name\n return bytes.subarray(0, PG_MAX_IDENTIFIER_BYTES).toString('utf8')\n}\n\n/**\n * Fails a deployment that believes it is schema-isolated but is not.\n *\n * Speaks up whenever a schema was resolved at all - the same resolution feeds the\n * pool's `search_path`, so the expectation and the connection can never disagree\n * by construction, and a deployment that configures isolation without setting\n * DB_SCHEMA is checked too. The case worth catching is a missing schema: Postgres\n * silently skips a `search_path` entry that does not exist and falls through to\n * `public`, which is the shared-catalog corruption this is here to prevent.\n */\nexport async function verifyDbSchema(\n poolToCheck: pg.Pool,\n configuredSchema?: string,\n): Promise<void> {\n const resolved = resolveDbSchema(configuredSchema)\n const result = await poolToCheck.query<{ schema: string | null }>(\n 'SELECT current_schema() AS schema',\n )\n const actual = result.rows[0]?.schema ?? null\n console.log(\n `[db] core schema: resolved=${resolved ?? '(none)'} current_schema=${actual ?? '(none)'}`,\n )\n if (!resolved) return\n if (actual !== truncateIdentifier(resolved)) {\n throw new Error(\n `The configured schema is \"${resolved}\" but the database resolved current_schema() to ` +\n `\"${actual}\". The schema is most likely missing, so every query would fall through to ` +\n `the shared catalog tables. Migrate this schema before starting the app.`,\n )\n }\n}\n\n/**\n * Gets the internal Prisma client instance.\n * Creates one if it doesn't exist.\n */\nexport function getDbClient(): PrismaClient {\n if (!prismaClient) {\n const databaseUrl = process.env.AC_CORE_DATABASE_URL\n if (!databaseUrl) {\n throw new Error(\n 'PrismaClient not initialized. You must call createAcMiddleware() before using database functions, ' +\n 'or set AC_CORE_DATABASE_URL environment variable for standalone usage.',\n )\n }\n\n const created = createCorePrismaClient({ connectionString: databaseUrl })\n pool = created.pool\n prismaClient = created.client\n }\n return prismaClient\n}\n\n/**\n * Sets the internal Prisma client instance.\n * Used by middleware to bridge with existing getDbClient() usage.\n */\nexport function setDbClient(client: PrismaClient): void {\n prismaClient = client\n}\n\n/**\n * Connects to the database.\n * Call this before performing database operations.\n */\nexport async function connectDb(): Promise<void> {\n const client = getDbClient()\n await client.$connect()\n if (pool) await verifyDbSchema(pool)\n}\n\n/**\n * Disconnects from the database.\n * Call this when done with database operations (e.g., in scripts).\n */\nexport async function disconnectDb(): Promise<void> {\n if (prismaClient) {\n await prismaClient.$disconnect()\n prismaClient = null\n }\n if (pool) {\n await pool.end()\n pool = null\n }\n}\n"],"mappings":";;;;;;AAKA,IAAI,eAAoC;AACxC,IAAI,OAAuB;;;;;;;;AAa3B,SAAgB,gBAAgB,kBAA+C;AAC7E,QAAO,QAAQ,IAAI,aAAa,oBAAoB;;;;;;;;;;AAWtD,SAAgB,qBAAqB,kBAEnC;CACA,MAAM,SAAS,gBAAgB,iBAAiB;AAChD,QAAO,SAAS,EAAE,SAAS,kBAAkB,UAAU,GAAG,EAAE;;;;;;;;;;;;;;AAe9D,SAAgB,uBAAuB,QAIiC;CACtE,MAAM,SAAS,gBAAgB,OAAO,iBAAiB;CAIvD,MAAM,MAAM,kBAAkB;CAC9B,MAAM,UAAU,IAAI,GAAG,KAAK;EAC1B,kBAAkB,OAAO;EACzB,GAAI,QAAQ,SAAY,EAAE,GAAG,EAAE,KAAK;EACpC,GAAG,qBAAqB,OAAO,iBAAiB;EACjD,CAAC;AAMF,QAAO;EAAE,QAJM,IAAI,aAAa;GAC9B,SAFc,IAAI,SAAS,SAAS,EAAE,QAAQ,CAAC;GAG/C,GAAI,OAAO,QAAQ,SAAY,EAAE,GAAG,EAAE,KAAK,OAAO,KAAK;GACxD,CAAC;EACe,MAAM;EAAS;EAAQ;;;AAI1C,MAAM,0BAA0B;;;;;;;;;AAUhC,SAAS,mBAAmB,MAAsB;CAChD,MAAM,QAAQ,OAAO,KAAK,MAAM,OAAO;AACvC,KAAI,MAAM,UAAU,wBAAyB,QAAO;AACpD,QAAO,MAAM,SAAS,GAAG,wBAAwB,CAAC,SAAS,OAAO;;;;;;;;;;;;AAapE,eAAsB,eACpB,aACA,kBACe;CACf,MAAM,WAAW,gBAAgB,iBAAiB;CAIlD,MAAM,UAHS,MAAM,YAAY,MAC/B,oCACD,EACqB,KAAK,IAAI,UAAU;AACzC,SAAQ,IACN,8BAA8B,YAAY,SAAS,kBAAkB,UAAU,WAChF;AACD,KAAI,CAAC,SAAU;AACf,KAAI,WAAW,mBAAmB,SAAS,CACzC,OAAM,IAAI,MACR,6BAA6B,SAAS,mDAChC,OAAO,oJAEd;;;;;;AAQL,SAAgB,cAA4B;AAC1C,KAAI,CAAC,cAAc;EACjB,MAAM,cAAc,QAAQ,IAAI;AAChC,MAAI,CAAC,YACH,OAAM,IAAI,MACR,2KAED;EAGH,MAAM,UAAU,uBAAuB,EAAE,kBAAkB,aAAa,CAAC;AACzE,SAAO,QAAQ;AACf,iBAAe,QAAQ;;AAEzB,QAAO;;;;;;AAOT,SAAgB,YAAY,QAA4B;AACtD,gBAAe;;;;;;AAOjB,eAAsB,YAA2B;AAE/C,OADe,aAAa,CACf,UAAU;AACvB,KAAI,KAAM,OAAM,eAAe,KAAK;;;;;;AAOtC,eAAsB,eAA8B;AAClD,KAAI,cAAc;AAChB,QAAM,aAAa,aAAa;AAChC,iBAAe;;AAEjB,KAAI,MAAM;AACR,QAAM,KAAK,KAAK;AAChB,SAAO"}
@@ -0,0 +1,5 @@
1
+ import { buildPgSchemaOptions, connectDb, createCorePrismaClient, disconnectDb, getDbClient, resolveDbSchema, setDbClient, verifyDbSchema } from "./client.mjs";
2
+ import { buildPgSslConfig } from "./sslConfig.mjs";
3
+ import { MakeTFromPrismaModel, ObjectKeys, ScalarFilter, ScalarKeys, TableSyncParamsPrisma, tableSyncPrisma } from "./tableSyncPrismaAdapter.mjs";
4
+ import { TABLE_SYNC_MAGAZINE, TableSyncMagazine, TableSyncMagazineModelNameKey } from "./tableSyncMagazine.mjs";
5
+ import { SyncAppCatalogResult, syncAppCatalog } from "./syncAppCatalog.mjs";
@@ -0,0 +1,28 @@
1
+ import { ConnectionOptions } from "node:tls";
2
+
3
+ //#region src/db/sslConfig.d.ts
4
+ /**
5
+ * Build the node-postgres `ssl` PoolConfig option from libpq-style env vars,
6
+ * because node-postgres does NOT do this correctly on its own for a
7
+ * connection-string pool: its built-in env handling maps `PGSSLMODE=verify-full`
8
+ * to a bare `ssl: true` (Node's default trust store) and IGNORES `PGSSLROOTCERT`
9
+ * — so an RDS server cert signed by the Amazon RDS CA would fail to verify.
10
+ *
11
+ * We therefore construct the `ssl` object explicitly and pass it to `new
12
+ * pg.Pool({ connectionString, ssl })`. Semantics mirror libpq / pg-connection-string:
13
+ * - verify-full → validate CA chain AND hostname (Node defaults; no overrides)
14
+ * - verify-ca → validate CA chain, skip hostname (checkServerIdentity no-op)
15
+ * - require/prefer → encrypt but don't validate (unless a CA is given → verify-ca-like)
16
+ * - no-verify → encrypt, don't validate (rejectUnauthorized:false)
17
+ * - disable / unset → no TLS
18
+ *
19
+ * `PGSSLROOTCERT` points at a CA bundle on disk (e.g. the Amazon RDS
20
+ * global-bundle.pem baked into the image).
21
+ *
22
+ * @returns a `ConnectionOptions` object, `false` for disabled, or `undefined`
23
+ * to leave SSL unset (let node-postgres fall back to its defaults).
24
+ */
25
+ declare function buildPgSslConfig(env?: NodeJS.ProcessEnv): ConnectionOptions | boolean | undefined;
26
+ //#endregion
27
+ export { buildPgSslConfig };
28
+ //# sourceMappingURL=sslConfig.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sslConfig.d.mts","names":[],"sources":["../../src/db/sslConfig.ts"],"mappings":";;;;;AAwBA;;;;;;;;;;;;;;;;;;;iBAAgB,gBAAA,CACd,GAAA,GAAK,MAAA,CAAO,UAAA,GACX,iBAAA"}
@@ -0,0 +1,71 @@
1
+ import { readFileSync } from "node:fs";
2
+
3
+ //#region src/db/sslConfig.ts
4
+ /**
5
+ * Build the node-postgres `ssl` PoolConfig option from libpq-style env vars,
6
+ * because node-postgres does NOT do this correctly on its own for a
7
+ * connection-string pool: its built-in env handling maps `PGSSLMODE=verify-full`
8
+ * to a bare `ssl: true` (Node's default trust store) and IGNORES `PGSSLROOTCERT`
9
+ * — so an RDS server cert signed by the Amazon RDS CA would fail to verify.
10
+ *
11
+ * We therefore construct the `ssl` object explicitly and pass it to `new
12
+ * pg.Pool({ connectionString, ssl })`. Semantics mirror libpq / pg-connection-string:
13
+ * - verify-full → validate CA chain AND hostname (Node defaults; no overrides)
14
+ * - verify-ca → validate CA chain, skip hostname (checkServerIdentity no-op)
15
+ * - require/prefer → encrypt but don't validate (unless a CA is given → verify-ca-like)
16
+ * - no-verify → encrypt, don't validate (rejectUnauthorized:false)
17
+ * - disable / unset → no TLS
18
+ *
19
+ * `PGSSLROOTCERT` points at a CA bundle on disk (e.g. the Amazon RDS
20
+ * global-bundle.pem baked into the image).
21
+ *
22
+ * @returns a `ConnectionOptions` object, `false` for disabled, or `undefined`
23
+ * to leave SSL unset (let node-postgres fall back to its defaults).
24
+ */
25
+ function buildPgSslConfig(env = process.env) {
26
+ const mode = env.PGSSLMODE;
27
+ const rootCertPath = env.PGSSLROOTCERT;
28
+ const readCa = () => {
29
+ if (!rootCertPath) return void 0;
30
+ try {
31
+ return readFileSync(rootCertPath, "utf8");
32
+ } catch (err) {
33
+ throw new Error(`PGSSLROOTCERT is set to "${rootCertPath}" but the CA bundle could not be read: ${err instanceof Error ? err.message : String(err)}`);
34
+ }
35
+ };
36
+ switch (mode) {
37
+ case "disable": return false;
38
+ case "verify-full": {
39
+ const ca = readCa();
40
+ if (!ca) throw new Error("PGSSLMODE=verify-full requires PGSSLROOTCERT to point at a CA bundle (node-postgres would otherwise use the system trust store, which does not include the Amazon RDS CA). Set PGSSLROOTCERT.");
41
+ return {
42
+ ca,
43
+ rejectUnauthorized: true
44
+ };
45
+ }
46
+ case "verify-ca": {
47
+ const ca = readCa();
48
+ if (!ca) throw new Error("PGSSLMODE=verify-ca requires PGSSLROOTCERT to point at a CA bundle.");
49
+ return {
50
+ ca,
51
+ rejectUnauthorized: true,
52
+ checkServerIdentity: () => void 0
53
+ };
54
+ }
55
+ case "require":
56
+ case "prefer": {
57
+ const ca = readCa();
58
+ return ca ? {
59
+ ca,
60
+ rejectUnauthorized: true,
61
+ checkServerIdentity: () => void 0
62
+ } : { rejectUnauthorized: false };
63
+ }
64
+ case "no-verify": return { rejectUnauthorized: false };
65
+ default: return;
66
+ }
67
+ }
68
+
69
+ //#endregion
70
+ export { buildPgSslConfig };
71
+ //# sourceMappingURL=sslConfig.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sslConfig.mjs","names":[],"sources":["../../src/db/sslConfig.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport type { ConnectionOptions } from 'node:tls'\n\n/**\n * Build the node-postgres `ssl` PoolConfig option from libpq-style env vars,\n * because node-postgres does NOT do this correctly on its own for a\n * connection-string pool: its built-in env handling maps `PGSSLMODE=verify-full`\n * to a bare `ssl: true` (Node's default trust store) and IGNORES `PGSSLROOTCERT`\n * — so an RDS server cert signed by the Amazon RDS CA would fail to verify.\n *\n * We therefore construct the `ssl` object explicitly and pass it to `new\n * pg.Pool({ connectionString, ssl })`. Semantics mirror libpq / pg-connection-string:\n * - verify-full → validate CA chain AND hostname (Node defaults; no overrides)\n * - verify-ca → validate CA chain, skip hostname (checkServerIdentity no-op)\n * - require/prefer → encrypt but don't validate (unless a CA is given → verify-ca-like)\n * - no-verify → encrypt, don't validate (rejectUnauthorized:false)\n * - disable / unset → no TLS\n *\n * `PGSSLROOTCERT` points at a CA bundle on disk (e.g. the Amazon RDS\n * global-bundle.pem baked into the image).\n *\n * @returns a `ConnectionOptions` object, `false` for disabled, or `undefined`\n * to leave SSL unset (let node-postgres fall back to its defaults).\n */\nexport function buildPgSslConfig(\n env: NodeJS.ProcessEnv = process.env,\n): ConnectionOptions | boolean | undefined {\n const mode = env.PGSSLMODE\n const rootCertPath = env.PGSSLROOTCERT\n\n const readCa = (): string | undefined => {\n if (!rootCertPath) return undefined\n try {\n return readFileSync(rootCertPath, 'utf8')\n } catch (err) {\n throw new Error(\n `PGSSLROOTCERT is set to \"${rootCertPath}\" but the CA bundle could not be read: ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n }\n }\n\n switch (mode) {\n case 'disable':\n return false\n\n case 'verify-full': {\n const ca = readCa()\n if (!ca) {\n throw new Error(\n 'PGSSLMODE=verify-full requires PGSSLROOTCERT to point at a CA bundle ' +\n '(node-postgres would otherwise use the system trust store, which does ' +\n 'not include the Amazon RDS CA). Set PGSSLROOTCERT.',\n )\n }\n // Node defaults: rejectUnauthorized=true (CA chain) + checkServerIdentity\n // (hostname). That IS verify-full.\n return { ca, rejectUnauthorized: true }\n }\n\n case 'verify-ca': {\n const ca = readCa()\n if (!ca) {\n throw new Error(\n 'PGSSLMODE=verify-ca requires PGSSLROOTCERT to point at a CA bundle.',\n )\n }\n // Validate the CA chain but skip hostname matching.\n return {\n ca,\n rejectUnauthorized: true,\n checkServerIdentity: () => undefined,\n }\n }\n\n case 'require':\n case 'prefer': {\n const ca = readCa()\n // With a CA, behave like verify-ca (validate chain, skip hostname);\n // without one, encrypt but don't validate.\n return ca\n ? { ca, rejectUnauthorized: true, checkServerIdentity: () => undefined }\n : { rejectUnauthorized: false }\n }\n\n case 'no-verify':\n return { rejectUnauthorized: false }\n\n default:\n // Unset / unknown: leave SSL unset so callers/pg keep prior behavior.\n return undefined\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,iBACd,MAAyB,QAAQ,KACQ;CACzC,MAAM,OAAO,IAAI;CACjB,MAAM,eAAe,IAAI;CAEzB,MAAM,eAAmC;AACvC,MAAI,CAAC,aAAc,QAAO;AAC1B,MAAI;AACF,UAAO,aAAa,cAAc,OAAO;WAClC,KAAK;AACZ,SAAM,IAAI,MACR,4BAA4B,aAAa,yCACvC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAEnD;;;AAIL,SAAQ,MAAR;EACE,KAAK,UACH,QAAO;EAET,KAAK,eAAe;GAClB,MAAM,KAAK,QAAQ;AACnB,OAAI,CAAC,GACH,OAAM,IAAI,MACR,gMAGD;AAIH,UAAO;IAAE;IAAI,oBAAoB;IAAM;;EAGzC,KAAK,aAAa;GAChB,MAAM,KAAK,QAAQ;AACnB,OAAI,CAAC,GACH,OAAM,IAAI,MACR,sEACD;AAGH,UAAO;IACL;IACA,oBAAoB;IACpB,2BAA2B;IAC5B;;EAGH,KAAK;EACL,KAAK,UAAU;GACb,MAAM,KAAK,QAAQ;AAGnB,UAAO,KACH;IAAE;IAAI,oBAAoB;IAAM,2BAA2B;IAAW,GACtE,EAAE,oBAAoB,OAAO;;EAGnC,KAAK,YACH,QAAO,EAAE,oBAAoB,OAAO;EAEtC,QAEE"}
@@ -0,0 +1,28 @@
1
+ import { ApprovalMethod } from "../types/common/approvalMethodTypes.mjs";
2
+ import { Group, Person } from "../types/common/personGroupTypes.mjs";
3
+ import { GroupingTagDefinition, Resource } from "../types/common/appCatalogTypes.mjs";
4
+
5
+ //#region src/db/syncAppCatalog.d.ts
6
+ interface SyncAppCatalogResult {
7
+ created: number;
8
+ updated: number;
9
+ deleted: number;
10
+ total: number;
11
+ }
12
+ /**
13
+ * Optional data to sync alongside the core app catalog.
14
+ */
15
+ interface SyncAppCatalogOptions {
16
+ persons?: Person[];
17
+ groups?: Group[];
18
+ }
19
+ /**
20
+ * Syncs app catalog data to the database using table sync.
21
+ * This will create new resources, update existing ones, and delete any that are no longer in the input.
22
+ *
23
+ * Note: Call connectDb() before and disconnectDb() after if running in a script.
24
+ */
25
+ declare function syncAppCatalog(resources: Resource[], tagsDefinitions: GroupingTagDefinition[], approvalMethods: ApprovalMethod[], screenshotsPath?: string, options?: SyncAppCatalogOptions): Promise<SyncAppCatalogResult>;
26
+ //#endregion
27
+ export { SyncAppCatalogResult, syncAppCatalog };
28
+ //# sourceMappingURL=syncAppCatalog.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syncAppCatalog.d.mts","names":[],"sources":["../../src/db/syncAppCatalog.ts"],"mappings":";;;;;UAeiB,oBAAA;EACf,OAAA;EACA,OAAA;EACA,OAAA;EACA,KAAA;AAAA;;;;UAuJe,qBAAA;EACf,OAAA,GAAU,MAAA;EACV,MAAA,GAAS,KAAA;AAAA;AAFX;;;;;;AAAA,iBAWsB,cAAA,CACpB,SAAA,EAAW,QAAA,IACX,eAAA,EAAiB,qBAAA,IACjB,eAAA,EAAiB,cAAA,IACjB,eAAA,WACA,OAAA,GAAU,qBAAA,GACT,OAAA,CAAQ,oBAAA"}
@@ -0,0 +1,242 @@
1
+ import { getDbClient } from "./client.mjs";
2
+ import { parseSourceSlug } from "../utils/parseSourceSlug.mjs";
3
+ import { tableSyncPrisma } from "./tableSyncPrismaAdapter.mjs";
4
+ import { TABLE_SYNC_MAGAZINE } from "./tableSyncMagazine.mjs";
5
+ import { upsertAsset } from "../modules/assets/upsertAsset.mjs";
6
+ import { naturalSort } from "../utils/naturalSort.mjs";
7
+ import { group } from "radashi";
8
+ import { readFile, readdir, stat } from "node:fs/promises";
9
+
10
+ //#region src/db/syncAppCatalog.ts
11
+ function isFileNotFoundError(error) {
12
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
13
+ }
14
+ async function processAssetDirectory(dirPath, appSlug, assetType, prisma) {
15
+ try {
16
+ const sortedFiles = naturalSort(await readdir(dirPath));
17
+ const assetIds = [];
18
+ for (let i = 0; i < sortedFiles.length; i++) {
19
+ const fileName = sortedFiles[i];
20
+ if (!fileName) continue;
21
+ const assetName = assetType === "screenshot" ? `${appSlug}-screenshot-${i + 1}` : `${appSlug}-icon`;
22
+ const id = await upsertAsset({
23
+ prisma,
24
+ buffer: await readFile(`${dirPath}/${fileName}`),
25
+ originalFilename: fileName,
26
+ name: assetName,
27
+ assetType
28
+ });
29
+ assetIds.push(id);
30
+ if (assetType === "icon") break;
31
+ }
32
+ return assetIds;
33
+ } catch (error) {
34
+ if (isFileNotFoundError(error)) return [];
35
+ throw error;
36
+ }
37
+ }
38
+ async function syncAppAssets(appSlug, appPath, prisma) {
39
+ return {
40
+ screenshotIds: await processAssetDirectory(`${appPath}/screenshots`, appSlug, "screenshot", prisma),
41
+ iconName: (await processAssetDirectory(`${appPath}/icons`, appSlug, "icon", prisma)).length > 0 ? `${appSlug}-icon` : null
42
+ };
43
+ }
44
+ async function syncAssetsFromFileSystem(resources, allAppsAssetsPath) {
45
+ const appDirectories = await readdir(allAppsAssetsPath);
46
+ const prisma = getDbClient();
47
+ const bySlug = group(resources, (a) => a.slug);
48
+ for (const appDirName of appDirectories) {
49
+ try {
50
+ if (!(await stat(`${allAppsAssetsPath}/${appDirName}`)).isDirectory()) continue;
51
+ } catch (error) {
52
+ if (isFileNotFoundError(error)) continue;
53
+ throw error;
54
+ }
55
+ const appSlug = appDirName;
56
+ if (!bySlug[appSlug]) throw new Error(`App '${appSlug}' does not exist in the app catalog. Existing apps: ${Object.keys(bySlug).join(", ")}`);
57
+ try {
58
+ const { screenshotIds, iconName } = await syncAppAssets(appSlug, `${allAppsAssetsPath}/${appDirName}`, prisma);
59
+ const updateData = {};
60
+ if (screenshotIds.length > 0) updateData.screenshotIds = screenshotIds;
61
+ if (iconName !== null) updateData.iconName = iconName;
62
+ if (Object.keys(updateData).length > 0) await prisma.dbResource.update({
63
+ where: { slug: appSlug },
64
+ data: updateData
65
+ });
66
+ } catch (error) {
67
+ const errorMessage = error instanceof Error ? error.message : String(error);
68
+ throw new Error(`Error while upserting assets for app '${appSlug}': ${errorMessage}`);
69
+ }
70
+ }
71
+ }
72
+ /**
73
+ * Syncs app catalog data to the database using table sync.
74
+ * This will create new resources, update existing ones, and delete any that are no longer in the input.
75
+ *
76
+ * Note: Call connectDb() before and disconnectDb() after if running in a script.
77
+ */
78
+ async function syncAppCatalog(resources, tagsDefinitions, approvalMethods, screenshotsPath, options) {
79
+ try {
80
+ const prisma = getDbClient();
81
+ if (options?.persons) {
82
+ const dbPersons = options.persons.map((p) => ({
83
+ slug: p.slug,
84
+ firstName: p.firstName,
85
+ lastName: p.lastName,
86
+ email: p.email ?? null
87
+ }));
88
+ await tableSyncPrisma({
89
+ prisma,
90
+ ...TABLE_SYNC_MAGAZINE.DbPerson
91
+ }).sync(dbPersons);
92
+ }
93
+ if (options?.groups) {
94
+ const dbGroups = options.groups.map((g) => ({
95
+ slug: g.slug,
96
+ displayName: g.displayName ?? null,
97
+ email: g.email ?? null
98
+ }));
99
+ await tableSyncPrisma({
100
+ prisma,
101
+ ...TABLE_SYNC_MAGAZINE.DbGroup
102
+ }).sync(dbGroups);
103
+ const allMemberships = options.groups.flatMap((g) => g.memberSlugs.map((personSlug) => ({
104
+ groupSlug: g.slug,
105
+ personSlug
106
+ })));
107
+ await tableSyncPrisma({
108
+ prisma,
109
+ ...TABLE_SYNC_MAGAZINE.DbGroupMembership
110
+ }).sync(allMemberships);
111
+ }
112
+ await tableSyncPrisma({
113
+ prisma,
114
+ ...TABLE_SYNC_MAGAZINE.DbApprovalMethod
115
+ }).sync(approvalMethods);
116
+ const sync = tableSyncPrisma({
117
+ prisma,
118
+ ...TABLE_SYNC_MAGAZINE.DbResource
119
+ });
120
+ await tableSyncPrisma({
121
+ prisma,
122
+ ...TABLE_SYNC_MAGAZINE.DbAppTagDefinition
123
+ }).sync(tagsDefinitions);
124
+ const uniqueSourceSlugs = /* @__PURE__ */ new Set();
125
+ for (const resource of resources) for (const source of resource.sources ?? []) {
126
+ const sourceSlug = parseSourceSlug(typeof source === "string" ? source : source.url);
127
+ uniqueSourceSlugs.add(sourceSlug);
128
+ }
129
+ const sources = Array.from(uniqueSourceSlugs).map((slug) => ({
130
+ slug,
131
+ userPrompt: null
132
+ }));
133
+ await tableSyncPrisma({
134
+ prisma,
135
+ ...TABLE_SYNC_MAGAZINE.Source
136
+ }).sync(sources);
137
+ const sortedResources = [...resources].sort((a, b) => {
138
+ return (a.parentSlug ? 1 : 0) - (b.parentSlug ? 1 : 0);
139
+ });
140
+ const dbResources = sortedResources.map((resource) => {
141
+ return {
142
+ slug: resource.slug || resource.displayName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""),
143
+ type: resource.type ?? "application",
144
+ displayName: resource.displayName,
145
+ abbreviation: resource.abbreviation ?? null,
146
+ nicknames: resource.nicknames ?? [],
147
+ description: resource.description,
148
+ teams: resource.teams ?? [],
149
+ accessRequest: resource.accessRequest ?? null,
150
+ notes: resource.notes ?? null,
151
+ tags: resource.tags ?? [],
152
+ appUrl: resource.appUrl ?? null,
153
+ links: resource.links ?? null,
154
+ iconName: resource.iconName ?? null,
155
+ screenshotIds: resource.screenshotIds ?? [],
156
+ deprecated: resource.deprecated ?? null,
157
+ aiPrompt: resource.aiPrompt ?? null,
158
+ urlIssues: resource.urlIssues ?? [],
159
+ tiers: resource.tiers ?? null,
160
+ parentSlug: resource.parentSlug ?? null,
161
+ tier: resource.tier ?? null,
162
+ aliases: resource.aliases ?? [],
163
+ ownerPersonSlug: resource.ownerPersonSlug ?? null,
164
+ approverSlugs: resource.approverSlugs ?? [],
165
+ accessComments: resource.accessComments ?? null,
166
+ extra: resource.extra ?? null,
167
+ lastCheckedAt: resource.lastCheckedAt ? new Date(resource.lastCheckedAt) : null,
168
+ nextCheckAfter: resource.nextCheckAfter ? new Date(resource.nextCheckAfter) : null,
169
+ lastContentChangeAt: resource.lastContentChangeAt ? new Date(resource.lastContentChangeAt) : null,
170
+ createdAt: resource.catalogAddedAt ? new Date(resource.catalogAddedAt) : void 0
171
+ };
172
+ });
173
+ const result = await sync.sync(dbResources);
174
+ for (const resource of resources) {
175
+ if (!resource.catalogAddedAt) continue;
176
+ const addedAt = new Date(resource.catalogAddedAt);
177
+ const slug = resource.slug || resource.displayName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
178
+ await prisma.$executeRaw`
179
+ UPDATE "DbResource"
180
+ SET "createdAt" = ${addedAt}
181
+ WHERE "slug" = ${slug} AND "createdAt" > ${addedAt}
182
+ `;
183
+ }
184
+ const slugs = dbResources.map((a) => a.slug);
185
+ const resourceRows = await prisma.dbResource.findMany({
186
+ where: { slug: { in: slugs } },
187
+ select: {
188
+ slug: true,
189
+ id: true
190
+ }
191
+ });
192
+ const slugToId = Object.fromEntries(resourceRows.map((r) => [r.slug, r.id]));
193
+ const allSourceRefs = sortedResources.flatMap((resource) => {
194
+ const resourceSlug = resource.slug || resource.displayName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
195
+ const resourceId = slugToId[resourceSlug];
196
+ if (!resourceId) throw new Error(`Resource '${resourceSlug}' has no id after sync. Existing slugs: ${Object.keys(slugToId).join(", ")}`);
197
+ return (resource.sources ?? []).map((source) => {
198
+ const url = typeof source === "string" ? source : source.url;
199
+ return {
200
+ resourceId,
201
+ sourceSlug: parseSourceSlug(url),
202
+ url,
203
+ parseDate: null,
204
+ excerpts: [],
205
+ userPrompt: null
206
+ };
207
+ });
208
+ });
209
+ await tableSyncPrisma({
210
+ prisma,
211
+ ...TABLE_SYNC_MAGAZINE.SourceReference
212
+ }).sync(allSourceRefs);
213
+ const resourcesWithAiMemorySeed = sortedResources.filter((r) => r.aiMemory != null && r.aiMemory.trim() !== "");
214
+ if (resourcesWithAiMemorySeed.length > 0) for (const resource of resourcesWithAiMemorySeed) {
215
+ const slug = resource.slug || resource.displayName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
216
+ await prisma.dbResource.updateMany({
217
+ where: {
218
+ slug,
219
+ aiMemory: null
220
+ },
221
+ data: { aiMemory: resource.aiMemory }
222
+ });
223
+ }
224
+ const actual = result.getActual();
225
+ if (screenshotsPath) await syncAssetsFromFileSystem(resources, screenshotsPath);
226
+ else console.warn("Do not sync screenhots");
227
+ return {
228
+ created: actual.length - resources.length + (resources.length - actual.length),
229
+ updated: 0,
230
+ deleted: 0,
231
+ total: actual.length
232
+ };
233
+ } catch (error) {
234
+ const errorMessage = error instanceof Error ? error.message : String(error);
235
+ const errorStack = error instanceof Error ? error.stack : void 0;
236
+ throw new Error(`Error syncing app catalog: ${errorMessage}\n\nDetails:\n${errorStack || "No stack trace available"}`);
237
+ }
238
+ }
239
+
240
+ //#endregion
241
+ export { syncAppCatalog };
242
+ //# sourceMappingURL=syncAppCatalog.mjs.map