@preventive/triage 1.0.0-alpha.5 → 1.0.0-alpha.50

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 (366) hide show
  1. package/LICENSE-APACHE +202 -0
  2. package/README.md +16 -0
  3. package/api/reap.ts +17 -0
  4. package/cli.js +6 -0
  5. package/client/dependency-paths.d.ts +3 -0
  6. package/client/dependency-paths.js +36 -0
  7. package/client/finding-link.js +305 -0
  8. package/client/linked-findings.d.ts +1 -0
  9. package/client/linked-findings.js +115 -0
  10. package/common/bundle-commit.d.ts +1 -0
  11. package/common/bundle-commit.js +4 -0
  12. package/common/bundle-compare-diff.d.ts +3 -0
  13. package/common/bundle-compare-diff.js +357 -0
  14. package/common/bundle-compare-inputs.d.ts +2 -0
  15. package/common/bundle-compare-inputs.js +65 -0
  16. package/common/bundle-compare-versions.js +172 -0
  17. package/common/bundle-comparison.d.ts +8 -0
  18. package/common/bundle-comparison.js +62 -0
  19. package/common/bundle-metadata.d.ts +15 -0
  20. package/common/bundle-metadata.js +226 -0
  21. package/common/bundle-pkg-of.d.ts +1 -0
  22. package/common/bundle-pkg-of.js +90 -0
  23. package/common/bundle-reasons.d.ts +2 -0
  24. package/common/bundle-reasons.js +21 -0
  25. package/common/bundle-source-links.d.ts +6 -0
  26. package/common/bundle-source-links.js +216 -0
  27. package/common/bundle-sourcemap.d.ts +10 -0
  28. package/common/bundle-sourcemap.js +100 -0
  29. package/common/bundle-sources.d.ts +7 -0
  30. package/common/bundle-sources.js +333 -0
  31. package/common/bundle-stats.js +41 -0
  32. package/common/bundle-tabs.js +1 -0
  33. package/common/code-language.js +65 -0
  34. package/common/default-scan-models.ts +31 -0
  35. package/common/dependency-reviews.d.ts +18 -0
  36. package/common/dependency-reviews.js +64 -0
  37. package/common/finding-duplicates.js +58 -0
  38. package/common/finding-filters.js +274 -0
  39. package/common/finding-id.js +47 -0
  40. package/common/github-issue-labels.d.ts +1 -0
  41. package/common/github-issue-labels.js +11 -0
  42. package/common/github-pr.ts +56 -0
  43. package/common/gzip.js +59 -0
  44. package/common/line-diff.d.ts +8 -0
  45. package/common/line-diff.js +57 -0
  46. package/common/linked-workspace-groups.js +63 -0
  47. package/common/managed/agent-tasks.ts +292 -0
  48. package/common/managed/comments.ts +34 -0
  49. package/common/managed/finding-detail.d.ts +2 -0
  50. package/common/managed/finding-detail.js +32 -0
  51. package/common/managed/github-agents.ts +114 -0
  52. package/common/managed/github-logins.ts +49 -0
  53. package/common/managed/npm-packages.d.ts +5 -0
  54. package/common/managed/npm-packages.js +34 -0
  55. package/common/managed/permissions.ts +31 -0
  56. package/common/managed/report-content.ts +72 -0
  57. package/common/managed/report-filter.ts +109 -0
  58. package/common/managed/repository-alias.ts +65 -0
  59. package/common/managed/roles.ts +35 -0
  60. package/common/managed/routes.d.ts +2 -0
  61. package/common/managed/routes.js +347 -0
  62. package/common/managed/scan-models.ts +6 -0
  63. package/common/managed/team-app.d.ts +5 -0
  64. package/common/managed/team-app.js +31 -0
  65. package/common/managed/triage.ts +83 -0
  66. package/common/managed/upload-seal.ts +126 -0
  67. package/common/pretty-print.d.ts +5 -0
  68. package/common/pretty-print.js +24 -0
  69. package/common/revalidation-conflicts.js +48 -0
  70. package/common/revalidation-input-groups.js +56 -0
  71. package/common/save-error-reason.ts +20 -7
  72. package/common/scan-server.ts +13 -0
  73. package/common/security-links.d.ts +5 -0
  74. package/common/security-links.js +33 -0
  75. package/common/server-info.ts +46 -0
  76. package/common/utf8.d.ts +3 -0
  77. package/common/utf8.js +45 -0
  78. package/common/workspace-app.js +39 -0
  79. package/common/workspace-groups.js +176 -0
  80. package/out/activity.js +4 -0
  81. package/out/brotli-fallback.js +3 -3
  82. package/out/chunk-2WDZPXVL.js +3 -0
  83. package/out/chunk-2XUEZHIP.js +28 -0
  84. package/out/chunk-2ZDZWLFY.js +1 -0
  85. package/out/chunk-4HI2MSAV.js +17 -0
  86. package/out/chunk-4XMP7YVH.js +36 -0
  87. package/out/chunk-6LNQTKAV.js +52 -0
  88. package/out/chunk-6S2U2UYG.js +232 -0
  89. package/out/chunk-75YLHBCM.js +3 -0
  90. package/out/chunk-7BKKYVWR.js +7 -0
  91. package/out/chunk-7C7DWL7I.js +1 -0
  92. package/out/chunk-7VAWINQF.js +6 -0
  93. package/out/chunk-C4Z6ZBXV.js +10 -0
  94. package/out/chunk-FC65TPUB.js +1 -0
  95. package/out/chunk-FKBXNUW4.js +1 -0
  96. package/out/chunk-GDPCLSOS.js +2 -0
  97. package/out/chunk-GO7FT4OD.js +1 -0
  98. package/out/chunk-HTBYHSPG.js +1 -0
  99. package/out/chunk-IYPY47MC.js +1 -0
  100. package/out/chunk-IZ7POWI3.js +18 -0
  101. package/out/chunk-JCIIBLKR.js +1 -0
  102. package/out/chunk-NRPC74AM.js +1 -0
  103. package/out/chunk-OOJM4CTU.js +1 -0
  104. package/out/chunk-PBZPYIG7.js +1 -0
  105. package/out/chunk-PNHBZGDD.js +1 -0
  106. package/out/chunk-PWFUOLZK.js +17 -0
  107. package/out/chunk-PXK7EU4V.js +1 -0
  108. package/out/chunk-QB4CBOX2.js +1 -0
  109. package/out/chunk-R3NZFXVR.js +1 -0
  110. package/out/chunk-R5O6FAW4.js +2 -0
  111. package/out/chunk-SXV2T5C4.js +80 -0
  112. package/out/chunk-TQJDYHVE.js +2 -0
  113. package/out/chunk-TUII34K2.js +1 -0
  114. package/out/chunk-UK7RGJT6.js +3 -0
  115. package/out/chunk-VFWMQ3LG.js +2 -0
  116. package/out/chunk-VH4XMZIW.js +2 -0
  117. package/out/chunk-WCPJDAYA.js +10 -0
  118. package/out/chunk-WFYLMF6Z.js +10 -0
  119. package/out/chunk-WYLQWYLA.js +1 -0
  120. package/out/client-managed-import.js +4 -0
  121. package/out/client-managed.js +33 -0
  122. package/out/client-sync.js +11 -17
  123. package/out/compare.js +35 -0
  124. package/out/dialogs.js +55 -0
  125. package/out/github-issue-authorized.html +3 -0
  126. package/out/github-issue-authorized.js +1 -0
  127. package/out/graph.js +17 -4
  128. package/out/index.html +90 -8
  129. package/out/prism.js +14 -10
  130. package/out/scan.js +2 -0
  131. package/out/stasis.svg +20 -0
  132. package/out/terminal.js +366 -39
  133. package/out/view.css +1 -1
  134. package/out/view.js +19 -144
  135. package/package.json +362 -59
  136. package/risk/actions/findings.js +161 -0
  137. package/risk/actions/resolve.js +219 -0
  138. package/risk/actions/scripts/commands.js +182 -0
  139. package/risk/actions/scripts/grade.js +113 -0
  140. package/risk/actions/scripts/packages.js +123 -0
  141. package/risk/actions/scripts/shell.js +169 -0
  142. package/risk/actions/uses.js +76 -0
  143. package/risk/actions/workflow.js +110 -0
  144. package/risk/files/characters.js +196 -0
  145. package/risk/files/hosts.js +205 -0
  146. package/risk/files/readability.js +177 -0
  147. package/risk/files/runs-code.js +615 -0
  148. package/risk/files/script.js +118 -0
  149. package/risk/files/stamps.js +47 -0
  150. package/risk/index.d.ts +254 -0
  151. package/risk/index.js +11 -0
  152. package/risk/npm/assess.js +285 -0
  153. package/risk/npm/combined.js +68 -0
  154. package/risk/npm/compare.js +257 -0
  155. package/risk/npm/dependencies.js +172 -0
  156. package/risk/npm/diff.js +79 -0
  157. package/risk/npm/downloads.js +84 -0
  158. package/risk/npm/files.js +445 -0
  159. package/risk/npm/held.js +63 -0
  160. package/risk/npm/install-scripts.js +34 -0
  161. package/risk/npm/malware.js +36 -0
  162. package/risk/npm/manifest.js +65 -0
  163. package/risk/npm/new-dependency.js +60 -0
  164. package/risk/npm/old-version.js +108 -0
  165. package/risk/npm/registry.js +388 -0
  166. package/risk/npm/release.js +85 -0
  167. package/risk/npm/score.js +59 -0
  168. package/risk/npm/specs.js +124 -0
  169. package/risk/npm/tarball.js +96 -0
  170. package/server-common/database-config.ts +16 -0
  171. package/server-common/initialize.ts +18 -0
  172. package/server-common/npm-advisories.ts +101 -0
  173. package/{server → server-common}/origin.ts +5 -5
  174. package/server-common/reap.ts +48 -0
  175. package/server-common/scan-config.ts +19 -0
  176. package/server-common/standalone.ts +29 -0
  177. package/server-common/storage-crypto.ts +182 -0
  178. package/server-common/storage-log.ts +38 -0
  179. package/server-common/vercel-blob.ts +103 -0
  180. package/server-e2e/app.ts +487 -0
  181. package/{server → server-e2e}/bus-receiver.ts +2 -1
  182. package/{server → server-e2e}/cli.js +9 -4
  183. package/{server → server-e2e}/config.ts +50 -42
  184. package/{server → server-e2e}/db-neon.ts +2 -2
  185. package/{server → server-e2e}/db-revision-sql.ts +1 -1
  186. package/{server → server-e2e}/db-stmt.ts +2 -2
  187. package/{server → server-e2e}/db.ts +7 -7
  188. package/{server → server-e2e}/http.ts +44 -13
  189. package/{server → server-e2e}/hub.ts +40 -9
  190. package/server-e2e/index.ts +17 -0
  191. package/server-e2e/lifecycle.ts +95 -0
  192. package/{server → server-e2e}/neon-driver.ts +2 -2
  193. package/{server → server-e2e}/npm-proxy.ts +12 -145
  194. package/{server → server-e2e}/objstore/blob-vercel.ts +10 -114
  195. package/{server → server-e2e}/objstore/blob.ts +1 -1
  196. package/server-e2e/objstore/fetch-mint-guard.ts +74 -0
  197. package/{server → server-e2e}/objstore/handlers.ts +9 -8
  198. package/{server → server-e2e}/objstore/init.ts +19 -27
  199. package/{server → server-e2e}/objstore/reaper.ts +1 -1
  200. package/server-e2e/objstore/rest-deny.ts +28 -0
  201. package/server-e2e/objstore/rest-mint.ts +224 -0
  202. package/{server → server-e2e}/objstore/rest.ts +3 -3
  203. package/{server → server-e2e}/objstore/store-neon.ts +6 -10
  204. package/{server → server-e2e}/objstore/store.ts +7 -7
  205. package/{server → server-e2e}/objstore/tokens.ts +2 -2
  206. package/{server → server-e2e}/pubsub.ts +12 -9
  207. package/{server → server-e2e}/sign.ts +4 -4
  208. package/{server → server-e2e}/sse-server.ts +60 -33
  209. package/{server → server-e2e}/static.ts +31 -21
  210. package/{server → server-e2e}/sync-handlers.ts +64 -30
  211. package/{server → server-e2e}/ws-server.ts +20 -9
  212. package/server-managed/activity.ts +258 -0
  213. package/server-managed/agent-merges.ts +109 -0
  214. package/server-managed/app-agents.ts +21 -0
  215. package/server-managed/avatar-store.ts +13 -0
  216. package/server-managed/blob-store.ts +17 -0
  217. package/server-managed/brotli.ts +10 -0
  218. package/server-managed/bundle-advisories.ts +120 -0
  219. package/server-managed/bundle-advisory-inventory.ts +77 -0
  220. package/server-managed/bundle-build-diagnostics.d.ts +10 -0
  221. package/server-managed/bundle-build-diagnostics.js +23 -0
  222. package/server-managed/bundle-build-leases.ts +41 -0
  223. package/server-managed/bundle-build-worker.js +115 -0
  224. package/server-managed/bundle-build.ts +200 -0
  225. package/server-managed/bundle-cache.ts +211 -0
  226. package/server-managed/bundle-catalog.ts +29 -0
  227. package/server-managed/bundle-commits.ts +140 -0
  228. package/server-managed/bundle-store.ts +24 -0
  229. package/server-managed/bundle-summary-cache.ts +130 -0
  230. package/server-managed/bundle.ts +119 -0
  231. package/server-managed/cache-storage.ts +20 -0
  232. package/server-managed/cli.js +21 -0
  233. package/server-managed/combined.ts +47 -0
  234. package/server-managed/comments.ts +151 -0
  235. package/server-managed/commit-coauthors.ts +106 -0
  236. package/server-managed/commit-lockfiles.ts +73 -0
  237. package/server-managed/comparison.ts +41 -0
  238. package/server-managed/config.ts +164 -0
  239. package/server-managed/content-access.ts +15 -0
  240. package/server-managed/crypto.ts +25 -0
  241. package/server-managed/db-methods.ts +1923 -0
  242. package/server-managed/db-neon.ts +458 -0
  243. package/server-managed/db-schema.ts +238 -0
  244. package/server-managed/db-table-names.ts +22 -0
  245. package/server-managed/db.ts +142 -0
  246. package/server-managed/dependabot-access.ts +60 -0
  247. package/server-managed/dependabot-alerts.ts +185 -0
  248. package/server-managed/dependency-review-comments.ts +100 -0
  249. package/server-managed/dependency-reviews.ts +346 -0
  250. package/server-managed/github-accounts.ts +60 -0
  251. package/server-managed/github-actions.ts +71 -0
  252. package/server-managed/github-app.ts +415 -0
  253. package/server-managed/github-errors.ts +70 -0
  254. package/server-managed/github-graphql.ts +39 -0
  255. package/server-managed/github-issue-links.ts +140 -0
  256. package/server-managed/github-issue-oauth.ts +47 -0
  257. package/server-managed/github-issues.ts +152 -0
  258. package/server-managed/github-link-oauth.ts +69 -0
  259. package/server-managed/github-links.ts +204 -0
  260. package/server-managed/github-metadata.ts +213 -0
  261. package/server-managed/github-oauth.ts +238 -0
  262. package/server-managed/github-pulls.ts +227 -0
  263. package/server-managed/github-requests.ts +39 -0
  264. package/server-managed/github-users.ts +30 -0
  265. package/server-managed/http-response.ts +76 -0
  266. package/server-managed/http.ts +5329 -0
  267. package/server-managed/import-triage.ts +51 -0
  268. package/server-managed/index.ts +192 -0
  269. package/server-managed/kept-briefly.ts +37 -0
  270. package/server-managed/kept.ts +28 -0
  271. package/server-managed/link-reports.ts +162 -0
  272. package/server-managed/locked-packages.ts +129 -0
  273. package/server-managed/lockfile-scope.ts +49 -0
  274. package/server-managed/lru.ts +30 -0
  275. package/server-managed/managed-issues.ts +78 -0
  276. package/server-managed/management-catalog.ts +45 -0
  277. package/server-managed/management.ts +99 -0
  278. package/server-managed/npm-delta-worker.js +23 -0
  279. package/server-managed/npm-delta.ts +194 -0
  280. package/server-managed/npm-insights.ts +449 -0
  281. package/server-managed/npm-kept.ts +215 -0
  282. package/server-managed/npm-loads.ts +264 -0
  283. package/server-managed/npm-manifest.ts +115 -0
  284. package/server-managed/npm-packages.ts +454 -0
  285. package/server-managed/npm-provenance.ts +123 -0
  286. package/server-managed/npm-risk.ts +285 -0
  287. package/server-managed/object-storage-disk.ts +148 -0
  288. package/server-managed/object-storage-vercel.ts +67 -0
  289. package/server-managed/object-storage.ts +75 -0
  290. package/server-managed/package-entry-points.ts +51 -0
  291. package/server-managed/pretty-print.ts +434 -0
  292. package/server-managed/public-workspace.ts +238 -0
  293. package/server-managed/pull-comment-reviews.ts +161 -0
  294. package/server-managed/pull-dependencies.ts +348 -0
  295. package/server-managed/pull-dependency-reviews.ts +328 -0
  296. package/server-managed/pull-reviews.ts +205 -0
  297. package/server-managed/queue.ts +39 -0
  298. package/server-managed/release-commits.ts +84 -0
  299. package/server-managed/rename-diff-worker.js +26 -0
  300. package/server-managed/rename-diff.ts +279 -0
  301. package/server-managed/repo-path.ts +21 -0
  302. package/server-managed/report-migration.ts +35 -0
  303. package/server-managed/report-query.ts +10 -0
  304. package/server-managed/report-response.ts +16 -0
  305. package/server-managed/report-sources.ts +178 -0
  306. package/server-managed/report-store.ts +85 -0
  307. package/server-managed/repository-actions.ts +218 -0
  308. package/server-managed/repository-agents.ts +355 -0
  309. package/server-managed/repository-aliases.ts +91 -0
  310. package/server-managed/repository-browser.ts +294 -0
  311. package/server-managed/repository-changes.ts +284 -0
  312. package/server-managed/repository-code.ts +215 -0
  313. package/server-managed/repository-dependency-reviews.ts +165 -0
  314. package/server-managed/repository-discovery.ts +82 -0
  315. package/server-managed/repository-history.ts +432 -0
  316. package/server-managed/repository-issues.ts +195 -0
  317. package/server-managed/repository-policy.ts +27 -0
  318. package/server-managed/repository-pulls.ts +156 -0
  319. package/server-managed/repository-pulse.ts +460 -0
  320. package/server-managed/repository-risk-assess.ts +105 -0
  321. package/server-managed/repository-risk-audit.ts +79 -0
  322. package/server-managed/repository-risk-read.ts +170 -0
  323. package/server-managed/repository-risk.ts +491 -0
  324. package/server-managed/repository-squashes.ts +168 -0
  325. package/server-managed/request-timing.ts +63 -0
  326. package/server-managed/reviewed-dependencies.ts +150 -0
  327. package/server-managed/revisions.ts +35 -0
  328. package/server-managed/scan-downloads.ts +87 -0
  329. package/server-managed/session.ts +132 -0
  330. package/server-managed/slugs.ts +38 -0
  331. package/server-managed/solidity-entry-points.ts +71 -0
  332. package/server-managed/sql-postgres.ts +34 -0
  333. package/server-managed/sql.ts +88 -0
  334. package/server-managed/static.ts +28 -0
  335. package/server-managed/storage-command.ts +16 -0
  336. package/server-managed/storage-db.ts +183 -0
  337. package/server-managed/storage-encryption.ts +290 -0
  338. package/server-managed/storage-maintenance.ts +259 -0
  339. package/server-managed/storage-payload.ts +121 -0
  340. package/server-managed/storage-stores.ts +91 -0
  341. package/server-managed/storage.ts +82 -0
  342. package/server-managed/team-app.ts +134 -0
  343. package/server-managed/team-catalog.ts +7 -0
  344. package/server-managed/team-feed.ts +180 -0
  345. package/server-managed/team-npm-scopes.ts +106 -0
  346. package/server-managed/team-reports.ts +307 -0
  347. package/server-managed/triage-response.ts +16 -0
  348. package/server-managed/type-only-worker.js +19 -0
  349. package/server-managed/type-only.ts +105 -0
  350. package/server-managed/uploads.ts +106 -0
  351. package/server-managed/upstream-cache.ts +243 -0
  352. package/server-managed/view-as.ts +58 -0
  353. package/server-managed/workspace-shares.ts +169 -0
  354. package/server.ts +70 -0
  355. package/server/index.ts +0 -480
  356. package/server/lifecycle.ts +0 -208
  357. /package/{LICENSE → LICENSE-MIT} +0 -0
  358. /package/{server → server-e2e}/auth.ts +0 -0
  359. /package/{server → server-e2e}/config.example.json +0 -0
  360. /package/{server → server-e2e}/objstore/blob-fs.ts +0 -0
  361. /package/{server → server-e2e}/objstore/fs.ts +0 -0
  362. /package/{server → server-e2e}/objstore/sign.ts +0 -0
  363. /package/{server → server-e2e}/peer.ts +0 -0
  364. /package/{server → server-e2e}/sse-session.ts +0 -0
  365. /package/{server → server-e2e}/util.ts +0 -0
  366. /package/{server → server-e2e}/validation.ts +0 -0
@@ -0,0 +1,458 @@
1
+ import { DEPENDENCY_REVIEW_COLUMNS, DEPENDENCY_REVIEW_SCHEMA } from './dependency-reviews.ts'
2
+ import { LINK_REPORT_SCHEMA } from './link-reports.ts'
3
+ import { REPOSITORY_ALIAS_SCHEMA } from './repository-aliases.ts'
4
+ import { TEAM_NPM_SCOPE_SCHEMA } from './team-npm-scopes.ts'
5
+ import { GITHUB_LINK_SCHEMA, GITHUB_LINK_USER_COLUMNS } from './github-links.ts'
6
+ import { REPOSITORY_DEPENDENCY_REVIEW_COLUMNS } from './repository-dependency-reviews.ts'
7
+ import { AsyncLocalStorage } from 'node:async_hooks'
8
+ import { WebSocket } from 'ws'
9
+ import { type ManagedDb, type ManagedDbOptions, createManagedMethods } from './db-methods.ts'
10
+ import { revisionSchema } from './revisions.ts'
11
+ import { MANAGED_SCHEMA, REPO_UPLOAD_INDEXES, VIEW_SESSION_INDEX } from './db-schema.ts'
12
+ import { STORAGE_SCHEMA } from './storage-db.ts'
13
+ import { GITHUB_COMMIT_SCHEMA, GITHUB_METADATA_SCHEMA, GITHUB_STATE_REASON_COLUMN } from './github-metadata.ts'
14
+ import { MANAGED_ISSUE_SCHEMA } from './managed-issues.ts'
15
+ import { BUNDLE_BUILD_LEASE_SCHEMA } from './bundle-build-leases.ts'
16
+ import { UPSTREAM_CACHE_SCHEMA } from './upstream-cache.ts'
17
+ import { COMMENT_SCHEMA } from './comments.ts'
18
+ import { ACTIVITY_SCHEMA, uploadAction } from './activity.ts'
19
+ import { type ManagedSqlDriver, scopeManagedMethods } from './sql.ts'
20
+ import { countStatement, timed } from './request-timing.ts'
21
+ import { postgresSchema, postgresSql } from './sql-postgres.ts'
22
+ import { managedTableRenames } from './db-table-names.ts'
23
+ import { WORKSPACE_SHARE_SCHEMA } from './workspace-shares.ts'
24
+ import { allocateMissingSlugs } from './slugs.ts'
25
+
26
+ export interface PgConnection {
27
+ query(sql: string, params?: unknown[]): Promise<{
28
+ rows: Record<string, unknown>[]; rowCount?: number | null
29
+ fields?: { name: string; dataTypeID: number }[]
30
+ }>
31
+ // After a failure the connection's state is unknown: discard it, never reuse it.
32
+ release(discard?: boolean): Promise<void>
33
+ }
34
+ export type PgConnect = () => Promise<PgConnection>
35
+ // Content/access mutations cooperate on this transaction-scoped lock. Reads,
36
+ // presence updates, and maintenance leases do not take it. Also fences migrations.
37
+ const LOCK = 'SELECT pg_advisory_xact_lock(1937006964, 1835101793)'
38
+
39
+ // Whether every version up to the latest is recorded, in one round trip at
40
+ // a cold start: a database with no versions table yet (undefined_table) has
41
+ // none. Outside a transaction, so the failed read aborts nothing.
42
+ const SCHEMA_VERSIONS = 32
43
+ async function currentSchema(db: PgConnection): Promise<boolean> {
44
+ try {
45
+ const { rows } = await db.query(`SELECT count(*) AS n FROM managed_schema_version WHERE version BETWEEN 1 AND ${SCHEMA_VERSIONS}`)
46
+ return Number(rows[0]?.['n']) === SCHEMA_VERSIONS
47
+ } catch (error) {
48
+ if ((error as { code?: unknown })?.code === '42P01') return false
49
+ throw error
50
+ }
51
+ }
52
+
53
+ async function migrateRepositoryDefaultCache(db: PgConnection): Promise<void> {
54
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 12')).rows.length > 0) return
55
+ await db.query('ALTER TABLE managed_selected_repo ADD COLUMN IF NOT EXISTS cached_default_branch TEXT')
56
+ await db.query('INSERT INTO managed_schema_version VALUES (12)')
57
+ }
58
+
59
+ async function migrateBundleBuildLeases(db: PgConnection): Promise<void> {
60
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 13')).rows.length > 0) return
61
+ await db.query(postgresSchema(BUNDLE_BUILD_LEASE_SCHEMA))
62
+ await db.query('INSERT INTO managed_schema_version VALUES (13)')
63
+ }
64
+
65
+ async function migrateGithubMetadata(db: PgConnection): Promise<void> {
66
+ await db.query(postgresSchema(GITHUB_METADATA_SCHEMA + MANAGED_ISSUE_SCHEMA))
67
+ await db.query(`ALTER TABLE managed_github_metadata ADD COLUMN IF NOT EXISTS state_reason ${GITHUB_STATE_REASON_COLUMN}`)
68
+ await db.query('ALTER TABLE managed_github_metadata ADD COLUMN IF NOT EXISTS attempted_at BIGINT')
69
+ await db.query('INSERT INTO managed_schema_version VALUES (14) ON CONFLICT DO NOTHING')
70
+ }
71
+
72
+ async function migrateBundleVisibility(db: PgConnection): Promise<void> {
73
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 15')).rows.length === 0) {
74
+ await db.query('ALTER TABLE managed_bundle ADD COLUMN IF NOT EXISTS visible INTEGER NOT NULL DEFAULT 1')
75
+ await db.query('INSERT INTO managed_schema_version VALUES (15)')
76
+ }
77
+ }
78
+
79
+ async function migrateLinkReports(db: PgConnection): Promise<void> {
80
+ await db.query(postgresSchema(revisionSchema(true)))
81
+ await db.query('INSERT INTO managed_schema_version VALUES (16) ON CONFLICT DO NOTHING')
82
+ }
83
+
84
+ async function migrateRepositoryAliases(db: PgConnection): Promise<void> {
85
+ await db.query(postgresSchema(REPOSITORY_ALIAS_SCHEMA))
86
+ await db.query('INSERT INTO managed_schema_version VALUES (17) ON CONFLICT DO NOTHING')
87
+ }
88
+
89
+ async function migrateHiddenTeams(db: PgConnection): Promise<void> {
90
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 18')).rows.length > 0) return
91
+ await db.query('ALTER TABLE managed_team ADD COLUMN IF NOT EXISTS hidden INTEGER NOT NULL DEFAULT 0')
92
+ await db.query('INSERT INTO managed_schema_version VALUES (18)')
93
+ }
94
+
95
+ async function migrateIssueFixes(db: PgConnection): Promise<void> {
96
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 19')).rows.length > 0) return
97
+ await db.query('ALTER TABLE managed_finding_issue ADD COLUMN IF NOT EXISTS auto_fix_url TEXT, ADD COLUMN IF NOT EXISTS auto_fix_checked_at BIGINT')
98
+ await db.query(postgresSchema(revisionSchema(true)))
99
+ await db.query('INSERT INTO managed_schema_version VALUES (19)')
100
+ }
101
+
102
+ // Existing rows stay NULL (unknown): some may have been built on the server.
103
+ async function migrateBundleProvenance(db: PgConnection): Promise<void> {
104
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 20')).rows.length > 0) return
105
+ await db.query('ALTER TABLE managed_bundle ADD COLUMN IF NOT EXISTS provenance TEXT')
106
+ await createUploadTrigger(db, 'bundle', false)
107
+ await db.query('INSERT INTO managed_schema_version VALUES (20)')
108
+ }
109
+
110
+ async function migrateUpstreamCache(db: PgConnection): Promise<void> {
111
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 21')).rows.length > 0) return
112
+ await db.query(postgresSchema(UPSTREAM_CACHE_SCHEMA))
113
+ await db.query('INSERT INTO managed_schema_version VALUES (21)')
114
+ }
115
+
116
+ // Existing builds stay NULL: their conditions were never recorded.
117
+ async function migrateBundleBuildConditions(db: PgConnection): Promise<void> {
118
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 22')).rows.length > 0) return
119
+ await db.query('ALTER TABLE managed_bundle ADD COLUMN IF NOT EXISTS build_conditions TEXT')
120
+ await db.query('INSERT INTO managed_schema_version VALUES (22)')
121
+ }
122
+
123
+ // Existing sessions create their upload key on first use.
124
+ async function migrateSessionUploadKeys(db: PgConnection): Promise<void> {
125
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 23')).rows.length > 0) return
126
+ await db.query('ALTER TABLE managed_session ADD COLUMN IF NOT EXISTS upload_key TEXT')
127
+ await db.query('INSERT INTO managed_schema_version VALUES (23)')
128
+ }
129
+
130
+ async function migrateViewSessions(db: PgConnection): Promise<void> {
131
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 24')).rows.length > 0) return
132
+ await db.query('ALTER TABLE managed_session ADD COLUMN IF NOT EXISTS viewer_session TEXT REFERENCES managed_session(id) ON DELETE CASCADE')
133
+ await db.query(VIEW_SESSION_INDEX)
134
+ await db.query('INSERT INTO managed_schema_version VALUES (24)')
135
+ }
136
+
137
+ async function migrateGithubCommits(db: PgConnection): Promise<void> {
138
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 25')).rows.length > 0) return
139
+ await db.query(postgresSchema(GITHUB_COMMIT_SCHEMA))
140
+ await db.query('INSERT INTO managed_schema_version VALUES (25)')
141
+ }
142
+
143
+ async function migrateTeamNpmScopes(db: PgConnection): Promise<void> {
144
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 26')).rows.length > 0) return
145
+ await db.query(postgresSchema(TEAM_NPM_SCOPE_SCHEMA))
146
+ await db.query('INSERT INTO managed_schema_version VALUES (26)')
147
+ }
148
+
149
+ async function migrateDependencyReviews(db: PgConnection): Promise<void> {
150
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 27')).rows.length > 0) return
151
+ await db.query('ALTER TABLE managed_team ADD COLUMN IF NOT EXISTS dependency_review INTEGER NOT NULL DEFAULT 0')
152
+ await db.query(postgresSchema(DEPENDENCY_REVIEW_SCHEMA))
153
+ await db.query('INSERT INTO managed_schema_version VALUES (27)')
154
+ }
155
+
156
+ // Existing names are the sign-in accounts' own: none was chosen yet.
157
+ async function migrateGithubLinks(db: PgConnection): Promise<void> {
158
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 28')).rows.length > 0) return
159
+ await db.query(postgresSchema(GITHUB_LINK_SCHEMA))
160
+ await db.query(`ALTER TABLE managed_user ${GITHUB_LINK_USER_COLUMNS.map(([column, type]) => `ADD COLUMN IF NOT EXISTS ${column} ${type}`).join(', ')}`)
161
+ await db.query('UPDATE managed_user SET github_name = name')
162
+ await db.query('INSERT INTO managed_schema_version VALUES (28)')
163
+ }
164
+
165
+ async function migrateRepositoryDependencyReviews(db: PgConnection): Promise<void> {
166
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 29')).rows.length > 0) return
167
+ await db.query(`ALTER TABLE managed_selected_repo ${REPOSITORY_DEPENDENCY_REVIEW_COLUMNS.map(([column, type]) => `ADD COLUMN IF NOT EXISTS ${column} ${type}`).join(', ')}`)
168
+ await db.query('INSERT INTO managed_schema_version VALUES (29)')
169
+ }
170
+
171
+ // The lockfiles a repository's dependency review holds to, its columns
172
+ // added since 29 added there too; and a team's published reviews, listed by
173
+ // it, indexed so.
174
+ async function migrateRepositoryDependencyReviewLockfiles(db: PgConnection): Promise<void> {
175
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 30')).rows.length > 0) return
176
+ await db.query(`ALTER TABLE managed_selected_repo ${REPOSITORY_DEPENDENCY_REVIEW_COLUMNS.map(([column, type]) => `ADD COLUMN IF NOT EXISTS ${column} ${type}`).join(', ')}`)
177
+ await db.query('CREATE INDEX IF NOT EXISTS managed_dependency_review_team_idx ON managed_dependency_review_team(team_id, review_id)')
178
+ await db.query('INSERT INTO managed_schema_version VALUES (30)')
179
+ }
180
+
181
+ async function migrateRepoUploadIndexes(db: PgConnection): Promise<void> {
182
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 31')).rows.length > 0) return
183
+ await db.query(REPO_UPLOAD_INDEXES)
184
+ await db.query('INSERT INTO managed_schema_version VALUES (31)')
185
+ }
186
+
187
+ // The shasums of the tarballs a dependency review reads, none known of
188
+ // those written before.
189
+ async function migrateDependencyReviewShasums(db: PgConnection): Promise<void> {
190
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 32')).rows.length > 0) return
191
+ await db.query(`ALTER TABLE managed_dependency_review ${DEPENDENCY_REVIEW_COLUMNS.map(([column, type]) => `ADD COLUMN IF NOT EXISTS ${column} ${type}`).join(', ')}`)
192
+ await db.query('INSERT INTO managed_schema_version VALUES (32)')
193
+ }
194
+
195
+ async function initialize(db: PgConnection): Promise<void> {
196
+ if (await currentSchema(db)) return
197
+ await db.query('BEGIN')
198
+ try {
199
+ await db.query(LOCK)
200
+ await db.query('CREATE TABLE IF NOT EXISTS managed_schema_version (version INTEGER PRIMARY KEY)')
201
+ const initialized = (await db.query('SELECT version FROM managed_schema_version WHERE version = 1')).rows.length > 0
202
+ const prefixed = (await db.query('SELECT version FROM managed_schema_version WHERE version = 3')).rows.length > 0
203
+ if (initialized && !prefixed) {
204
+ const { rows } = await db.query("SELECT relname AS name FROM pg_class WHERE relkind IN ('r', 'p') AND pg_table_is_visible(oid)")
205
+ for (const [from, to] of managedTableRenames(new Set(rows.map(row => String(row['name']))))) {
206
+ await db.query(`ALTER TABLE ${from} RENAME TO ${to}`)
207
+ }
208
+ // PL/pgSQL function bodies retain literal table names after ALTER TABLE.
209
+ for (const type of ['report', 'bundle']) await createUploadTrigger(db, type, false)
210
+ }
211
+ if (!initialized) {
212
+ await db.query(postgresSchema(MANAGED_SCHEMA + COMMENT_SCHEMA + ACTIVITY_SCHEMA))
213
+ await db.query(`ALTER TABLE managed_finding_triage_event ADD COLUMN report_id TEXT, ADD COLUMN report TEXT, ADD COLUMN repo TEXT;
214
+ CREATE UNIQUE INDEX managed_team_slug_idx ON managed_team(slug);
215
+ CREATE UNIQUE INDEX managed_report_slug_idx ON managed_report(slug);
216
+ CREATE INDEX managed_activity_actor_at_idx ON managed_activity(actor_id, at);`)
217
+ for (const type of ['report', 'bundle']) await createUploadTrigger(db, type)
218
+ await db.query('INSERT INTO managed_schema_version VALUES (1)')
219
+ }
220
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 2')).rows.length === 0) {
221
+ await db.query('CREATE INDEX IF NOT EXISTS managed_report_bundle_hash_idx ON managed_report(bundle_id, sha256)')
222
+ await db.query('INSERT INTO managed_schema_version VALUES (2)')
223
+ }
224
+ if (!prefixed) await db.query('INSERT INTO managed_schema_version VALUES (3)')
225
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 4')).rows.length === 0) {
226
+ await db.query(postgresSchema(WORKSPACE_SHARE_SCHEMA))
227
+ await db.query('INSERT INTO managed_schema_version VALUES (4)')
228
+ }
229
+ await migrateGithubMetadata(db)
230
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 5')).rows.length === 0) {
231
+ await db.query(`ALTER TABLE managed_workspace_share ADD COLUMN IF NOT EXISTS dependencies INTEGER NOT NULL DEFAULT 0,
232
+ ADD COLUMN IF NOT EXISTS security INTEGER NOT NULL DEFAULT 0`)
233
+ await db.query('INSERT INTO managed_schema_version VALUES (5)')
234
+ }
235
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 6')).rows.length === 0) {
236
+ await db.query('ALTER TABLE managed_bundle ADD COLUMN IF NOT EXISTS slug TEXT')
237
+ const { rows } = await db.query('SELECT id, slug FROM managed_bundle ORDER BY id')
238
+ for (const row of allocateMissingSlugs(rows as { id: string; slug: string | null }[])) {
239
+ await db.query('UPDATE managed_bundle SET slug = $1 WHERE id = $2', [row.slug, row.id])
240
+ }
241
+ await db.query(`ALTER TABLE managed_bundle ALTER COLUMN slug SET NOT NULL;
242
+ CREATE UNIQUE INDEX IF NOT EXISTS managed_bundle_slug_idx ON managed_bundle(slug);
243
+ INSERT INTO managed_schema_version VALUES (6)`)
244
+ }
245
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 7')).rows.length === 0) {
246
+ await db.query("ALTER TABLE managed_bundle ADD COLUMN IF NOT EXISTS repo_directory TEXT NOT NULL DEFAULT ''")
247
+ await db.query('INSERT INTO managed_schema_version VALUES (7)')
248
+ }
249
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 8')).rows.length === 0) {
250
+ await db.query('CREATE INDEX IF NOT EXISTS managed_report_hash_idx ON managed_report(sha256, uploaded_at, id)')
251
+ await db.query('INSERT INTO managed_schema_version VALUES (8)')
252
+ }
253
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 9')).rows.length === 0) {
254
+ await db.query(postgresSchema(STORAGE_SCHEMA))
255
+ for (const table of ['managed_report', 'managed_bundle']) {
256
+ await db.query(`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS data_key TEXT, ADD COLUMN IF NOT EXISTS storage_encrypted INTEGER NOT NULL DEFAULT 0`)
257
+ }
258
+ await db.query('ALTER TABLE managed_user ADD COLUMN IF NOT EXISTS gh_tokens_encrypted INTEGER NOT NULL DEFAULT 0')
259
+ await db.query('INSERT INTO managed_schema_version VALUES (9)')
260
+ }
261
+ await db.query(postgresSchema(LINK_REPORT_SCHEMA))
262
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 10')).rows.length === 0) {
263
+ await db.query(postgresSchema(revisionSchema(true)) + '; INSERT INTO managed_schema_version VALUES (10)')
264
+ }
265
+ if ((await db.query('SELECT version FROM managed_schema_version WHERE version = 11')).rows.length === 0) {
266
+ await db.query(postgresSchema(revisionSchema(true)) + '; INSERT INTO managed_schema_version VALUES (11)')
267
+ }
268
+ for (const migrate of [migrateRepositoryDefaultCache, migrateBundleBuildLeases, migrateBundleVisibility, migrateLinkReports, migrateRepositoryAliases, migrateHiddenTeams, migrateIssueFixes, migrateBundleProvenance, migrateUpstreamCache, migrateBundleBuildConditions, migrateSessionUploadKeys, migrateViewSessions, migrateGithubCommits, migrateTeamNpmScopes, migrateDependencyReviews, migrateGithubLinks, migrateRepositoryDependencyReviews,
269
+ migrateRepositoryDependencyReviewLockfiles, migrateRepoUploadIndexes, migrateDependencyReviewShasums]) await migrate(db)
270
+ await db.query('COMMIT')
271
+ } catch (err) {
272
+ await db.query('ROLLBACK')
273
+ throw err
274
+ }
275
+ }
276
+
277
+ async function createUploadTrigger(db: PgConnection, type: string, createTrigger = true): Promise<void> {
278
+ await db.query(`CREATE OR REPLACE FUNCTION managed_${type}_activity_insert() RETURNS trigger LANGUAGE plpgsql AS $$
279
+ BEGIN
280
+ INSERT INTO managed_activity (id, kind, actor, action, repo, report_id, report, at, bundle_id, actor_id)
281
+ VALUES ('${type}-upload:' || NEW.id, 'upload',
282
+ COALESCE(NEW.uploaded_by_login, (SELECT login FROM managed_user WHERE id = NEW.uploaded_by)),
283
+ ${uploadAction(type, 'NEW')}, (SELECT full_name FROM managed_selected_repo WHERE repo_id = NEW.repo_id),
284
+ ${type === 'report' ? 'NEW.id' : 'NULL'}, NEW.filename, NEW.uploaded_at,
285
+ ${type === 'bundle' ? 'NEW.id' : 'NULL'}, NEW.uploaded_by);
286
+ RETURN NEW;
287
+ END $$;`)
288
+ if (createTrigger) {
289
+ await db.query(`CREATE TRIGGER managed_${type}_activity AFTER INSERT ON managed_${type}
290
+ FOR EACH ROW EXECUTE FUNCTION managed_${type}_activity_insert();`)
291
+ }
292
+ }
293
+
294
+ function requestConnections(connect: PgConnect) {
295
+ const requests = new AsyncLocalStorage<{ connection?: PgConnection; queue: Promise<unknown>; ended: boolean }>()
296
+ async function withConnection<T>(work: (db: PgConnection) => Promise<T>): Promise<T> {
297
+ const request = requests.getStore()
298
+ if (!request || request.ended) {
299
+ const connection = await connect()
300
+ let result: T
301
+ try { result = await work(connection) }
302
+ catch (error) { await connection.release(true).catch(() => {}); throw error }
303
+ await connection.release()
304
+ return result
305
+ }
306
+ // Serialize transactions on the leased connection, never interleave BEGINs
307
+ // from concurrent Blob/catalog tasks. Remote Blob reads remain concurrent.
308
+ const result = request.queue.then(async () => {
309
+ const connection = request.connection ??= await connect()
310
+ try { return await work(connection) }
311
+ catch (error) {
312
+ delete request.connection
313
+ await connection.release(true).catch(() => {})
314
+ throw error
315
+ }
316
+ })
317
+ request.queue = result.catch(() => {})
318
+ return result
319
+ }
320
+ async function withRequest<T>(work: () => Promise<T>): Promise<T> {
321
+ if (requests.getStore()) return work()
322
+ const request: { connection?: PgConnection; queue: Promise<unknown>; ended: boolean } = { queue: Promise.resolve(), ended: false }
323
+ let failed = false
324
+ try { return await requests.run(request, work) }
325
+ catch (error) { failed = true; throw error }
326
+ finally {
327
+ request.ended = true
328
+ await request.queue
329
+ if (failed) await request.connection?.release(true).catch(() => {})
330
+ else await request.connection?.release()
331
+ }
332
+ }
333
+ return { withConnection, withRequest }
334
+ }
335
+
336
+ // Exposed for parity tests against real PostgreSQL semantics via PGlite.
337
+ export async function openPostgresManagedDb(leased: PgConnect, options: ManagedDbOptions = {}, reuseConnections = false): Promise<ManagedDb> {
338
+ // Each statement told in its request's Server-Timing (request-timing.ts).
339
+ const connect: PgConnect = async () => {
340
+ const connection = await leased()
341
+ return { query: (sql, params) => { countStatement(); return connection.query(sql, params) }, release: discard => connection.release(discard) }
342
+ }
343
+ const initial = await connect()
344
+ try { await initialize(initial) } catch (err) { await initial.release(true).catch(() => {}); throw err }
345
+ await initial.release()
346
+ const { withConnection, withRequest } = requestConnections(connect)
347
+ const context = new AsyncLocalStorage<PgConnection>()
348
+ let closed = false
349
+ const driver: ManagedSqlDriver = {
350
+ prepare(source) {
351
+ const { sql, names } = postgresSql(source)
352
+ async function query(args: unknown[]) {
353
+ const db = context.getStore()
354
+ if (!db) throw new Error('Managed query outside transaction')
355
+ const values = names.length > 0 ? names.map(name => (args[0] as Record<string, unknown>)[name])
356
+ : args.length === 1 && typeof args[0] === 'object' && args[0] !== null ? [] : args
357
+ const result = await db.query(sql, values)
358
+ // Postgres int8 is returned as text by node-postgres/Neon. All store
359
+ // numbers are JS safe integers (timestamps, IDs, sizes and sequences).
360
+ for (const field of result.fields ?? []) {
361
+ if (field.dataTypeID !== 20) continue
362
+ for (const row of result.rows) {
363
+ if (row[field.name] == null) continue
364
+ const value = Number(row[field.name])
365
+ if (!Number.isSafeInteger(value)) throw new Error('Managed integer exceeds safe range')
366
+ row[field.name] = value
367
+ }
368
+ }
369
+ return result
370
+ }
371
+ return {
372
+ get: async (...args) => (await query(args)).rows[0],
373
+ all: async (...args) => (await query(args)).rows,
374
+ run: async (...args) => ({ changes: (await query(args)).rowCount ?? 0 }),
375
+ }
376
+ },
377
+ async scope(write, work, { lock = write, statement = false } = {}) {
378
+ if (closed) throw new Error('Managed database is closed')
379
+ let committed = false
380
+ try { return await withConnection(async db => {
381
+ if (statement) return context.run(db, work)
382
+ let commitAttempted = false
383
+ try {
384
+ await db.query(write ? 'BEGIN' : 'BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY')
385
+ if (lock) await db.query(LOCK)
386
+ const result = await context.run(db, work)
387
+ commitAttempted = true
388
+ await db.query('COMMIT')
389
+ committed = true
390
+ return result
391
+ } catch (err) {
392
+ await db.query('ROLLBACK').catch(() => {})
393
+ if (write && commitAttempted) throw new Error('Managed commit outcome is uncertain', { cause: err })
394
+ throw err
395
+ }
396
+ }) } catch (error) {
397
+ if (write && committed) throw new Error('Managed commit outcome is uncertain', { cause: error })
398
+ throw error
399
+ }
400
+ },
401
+ close() { closed = true },
402
+ }
403
+ const methods = scopeManagedMethods(createManagedMethods(driver, options), driver)
404
+ if (reuseConnections) methods.withRequest = withRequest
405
+ return methods
406
+ }
407
+
408
+ // Idle connections stay open this long between leases, so a warm instance's
409
+ // requests and feed polls (three seconds apart) skip the TLS, WebSocket and
410
+ // Postgres handshake a new connection repeats.
411
+ const NEON_POOL_IDLE_MS = 5_000
412
+
413
+ type NeonPoolClient = {
414
+ query: PgConnection['query']
415
+ release(discard?: boolean): void
416
+ on(event: string, listener: (...args: unknown[]) => void): void
417
+ }
418
+ export type NeonPool = {
419
+ connect(): Promise<NeonPoolClient>
420
+ end(): Promise<void>
421
+ on(event: string, listener: (...args: unknown[]) => void): void
422
+ }
423
+
424
+ // `attachPool` is Vercel's attachDatabasePool on serverless instances: each
425
+ // release keeps the invocation alive until idle connections have closed, so
426
+ // no socket survives a frozen instance.
427
+ export async function openNeonManagedDb(url: string, options: ManagedDbOptions = {}, attachPool?: (pool: NeonPool) => void): Promise<ManagedDb> {
428
+ // Reuse the e2e optional-driver boundary.
429
+ const { Pool, neonConfig } = await import('../server-e2e/neon-driver.ts') as unknown as {
430
+ Pool: new (config: { connectionString: string; max: number; idleTimeoutMillis: number }) => NeonPool
431
+ neonConfig: { webSocketConstructor: typeof WebSocket }
432
+ }
433
+ neonConfig.webSocketConstructor = WebSocket
434
+ // Leases stay unbounded, one per request or operation as before: a cap would
435
+ // stall a request that holds one while it awaits work needing another.
436
+ const pool = new Pool({ connectionString: url, max: Infinity, idleTimeoutMillis: NEON_POOL_IDLE_MS })
437
+ // The pool evicts an idle connection the server closed. Neither that nor a
438
+ // remote disconnect while leased may become an unhandled EventEmitter error;
439
+ // a leased one must instead reject its next query, and is then discarded.
440
+ pool.on('error', () => {})
441
+ attachPool?.(pool)
442
+ const failed = new WeakSet<NeonPoolClient>(), watched = new WeakSet<NeonPoolClient>()
443
+ let ending: Promise<void> | undefined
444
+ const end = () => ending ??= pool.end()
445
+ let db: ManagedDb
446
+ try {
447
+ db = await openPostgresManagedDb(async () => {
448
+ // A lease's wait told in its request's Server-Timing: a new connection's
449
+ // TLS, WebSocket and Postgres handshake, where the pool had none idle.
450
+ const client = await timed('conn', () => pool.connect())
451
+ if (!watched.has(client)) { watched.add(client); client.on('error', () => { failed.add(client) }) }
452
+ return { query: (sql, params) => client.query(sql, params),
453
+ release: discard => Promise.try(() => client.release(discard === true || failed.has(client))) }
454
+ }, options, true)
455
+ } catch (err) { await end().catch(() => {}); throw err }
456
+ const close = db.close
457
+ return { ...db, async close() { try { await close() } finally { await end() } } }
458
+ }