@homeflare/alchemy 0.42.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (648) hide show
  1. package/README.md +10 -1
  2. package/dist/caddy/index.js +35 -1
  3. package/dist/caddy/index.js.map +3 -3
  4. package/dist/launchd/index.js +35 -1
  5. package/dist/launchd/index.js.map +3 -3
  6. package/dist/linux/index.js +35 -1
  7. package/dist/linux/index.js.map +3 -3
  8. package/dist/litellm/access-group-form.d.ts +26 -0
  9. package/dist/litellm/access-group-form.d.ts.map +1 -0
  10. package/dist/litellm/access-group-operations.d.ts +37 -0
  11. package/dist/litellm/access-group-operations.d.ts.map +1 -0
  12. package/dist/litellm/access-group-types.d.ts +49 -0
  13. package/dist/litellm/access-group-types.d.ts.map +1 -0
  14. package/dist/litellm/access-group.d.ts +55 -0
  15. package/dist/litellm/access-group.d.ts.map +1 -0
  16. package/dist/litellm/budget-errors.d.ts +36 -0
  17. package/dist/litellm/budget-errors.d.ts.map +1 -0
  18. package/dist/litellm/budget-form.d.ts +50 -0
  19. package/dist/litellm/budget-form.d.ts.map +1 -0
  20. package/dist/litellm/budget-operations.d.ts +20 -0
  21. package/dist/litellm/budget-operations.d.ts.map +1 -0
  22. package/dist/litellm/budget.d.ts +61 -0
  23. package/dist/litellm/budget.d.ts.map +1 -0
  24. package/dist/litellm/credential-errors.d.ts +131 -0
  25. package/dist/litellm/credential-errors.d.ts.map +1 -0
  26. package/dist/litellm/credential-form.d.ts +79 -0
  27. package/dist/litellm/credential-form.d.ts.map +1 -0
  28. package/dist/litellm/credential-operations.d.ts +84 -0
  29. package/dist/litellm/credential-operations.d.ts.map +1 -0
  30. package/dist/litellm/credential-rewrite.d.ts +6 -0
  31. package/dist/litellm/credential-rewrite.d.ts.map +1 -0
  32. package/dist/litellm/credential-types.d.ts +87 -0
  33. package/dist/litellm/credential-types.d.ts.map +1 -0
  34. package/dist/litellm/credential-values.d.ts +53 -0
  35. package/dist/litellm/credential-values.d.ts.map +1 -0
  36. package/dist/litellm/credential.d.ts +83 -0
  37. package/dist/litellm/credential.d.ts.map +1 -0
  38. package/dist/litellm/fake-access-group-litellm.d.ts +34 -0
  39. package/dist/litellm/fake-access-group-litellm.d.ts.map +1 -0
  40. package/dist/litellm/fake-credential-litellm.d.ts +54 -0
  41. package/dist/litellm/fake-credential-litellm.d.ts.map +1 -0
  42. package/dist/litellm/fake-keys.d.ts +53 -0
  43. package/dist/litellm/fake-keys.d.ts.map +1 -0
  44. package/dist/litellm/fake-litellm.d.ts +9 -1
  45. package/dist/litellm/fake-litellm.d.ts.map +1 -1
  46. package/dist/litellm/fake-mcp-litellm.d.ts +67 -0
  47. package/dist/litellm/fake-mcp-litellm.d.ts.map +1 -0
  48. package/dist/litellm/fake-model-litellm-post-update.d.ts +49 -0
  49. package/dist/litellm/fake-model-litellm-post-update.d.ts.map +1 -0
  50. package/dist/litellm/fake-model-litellm.d.ts +48 -0
  51. package/dist/litellm/fake-model-litellm.d.ts.map +1 -0
  52. package/dist/litellm/fake-policy-attachment-litellm.d.ts +33 -0
  53. package/dist/litellm/fake-policy-attachment-litellm.d.ts.map +1 -0
  54. package/dist/litellm/fake-policy-litellm.d.ts +32 -0
  55. package/dist/litellm/fake-policy-litellm.d.ts.map +1 -0
  56. package/dist/litellm/fake-registry-base.d.ts +42 -0
  57. package/dist/litellm/fake-registry-base.d.ts.map +1 -0
  58. package/dist/litellm/fake-stack.d.ts +8 -1
  59. package/dist/litellm/fake-stack.d.ts.map +1 -1
  60. package/dist/litellm/fake-team-litellm.d.ts +47 -0
  61. package/dist/litellm/fake-team-litellm.d.ts.map +1 -0
  62. package/dist/litellm/fake-tool-litellm.d.ts +28 -0
  63. package/dist/litellm/fake-tool-litellm.d.ts.map +1 -0
  64. package/dist/litellm/fake-toolset-litellm.d.ts +29 -0
  65. package/dist/litellm/fake-toolset-litellm.d.ts.map +1 -0
  66. package/dist/litellm/index.d.ts +20 -0
  67. package/dist/litellm/index.d.ts.map +1 -1
  68. package/dist/litellm/index.js +3201 -2
  69. package/dist/litellm/index.js.map +70 -5
  70. package/dist/litellm/key-errors.d.ts +169 -0
  71. package/dist/litellm/key-errors.d.ts.map +1 -0
  72. package/dist/litellm/key-form.d.ts +119 -0
  73. package/dist/litellm/key-form.d.ts.map +1 -0
  74. package/dist/litellm/key-harness.d.ts +41 -0
  75. package/dist/litellm/key-harness.d.ts.map +1 -0
  76. package/dist/litellm/key-metadata-errors.d.ts +30 -0
  77. package/dist/litellm/key-metadata-errors.d.ts.map +1 -0
  78. package/dist/litellm/key-metadata.d.ts +22 -0
  79. package/dist/litellm/key-metadata.d.ts.map +1 -0
  80. package/dist/litellm/key-operations.d.ts +63 -0
  81. package/dist/litellm/key-operations.d.ts.map +1 -0
  82. package/dist/litellm/key-secret.d.ts +33 -0
  83. package/dist/litellm/key-secret.d.ts.map +1 -0
  84. package/dist/litellm/key.d.ts +78 -0
  85. package/dist/litellm/key.d.ts.map +1 -0
  86. package/dist/litellm/mcp-server-credential.d.ts +50 -0
  87. package/dist/litellm/mcp-server-credential.d.ts.map +1 -0
  88. package/dist/litellm/mcp-server-errors.d.ts +101 -0
  89. package/dist/litellm/mcp-server-errors.d.ts.map +1 -0
  90. package/dist/litellm/mcp-server-form.d.ts +56 -0
  91. package/dist/litellm/mcp-server-form.d.ts.map +1 -0
  92. package/dist/litellm/mcp-server-locate.d.ts +26 -0
  93. package/dist/litellm/mcp-server-locate.d.ts.map +1 -0
  94. package/dist/litellm/mcp-server-operations.d.ts +63 -0
  95. package/dist/litellm/mcp-server-operations.d.ts.map +1 -0
  96. package/dist/litellm/mcp-server-shadow.d.ts +24 -0
  97. package/dist/litellm/mcp-server-shadow.d.ts.map +1 -0
  98. package/dist/litellm/mcp-server-types.d.ts +118 -0
  99. package/dist/litellm/mcp-server-types.d.ts.map +1 -0
  100. package/dist/litellm/mcp-server-url.d.ts +11 -0
  101. package/dist/litellm/mcp-server-url.d.ts.map +1 -0
  102. package/dist/litellm/mcp-server.d.ts +95 -0
  103. package/dist/litellm/mcp-server.d.ts.map +1 -0
  104. package/dist/litellm/model-claim.d.ts +24 -0
  105. package/dist/litellm/model-claim.d.ts.map +1 -0
  106. package/dist/litellm/model-credential.d.ts +12 -0
  107. package/dist/litellm/model-credential.d.ts.map +1 -0
  108. package/dist/litellm/model-errors.d.ts +119 -0
  109. package/dist/litellm/model-errors.d.ts.map +1 -0
  110. package/dist/litellm/model-form.d.ts +78 -0
  111. package/dist/litellm/model-form.d.ts.map +1 -0
  112. package/dist/litellm/model-locate.d.ts +13 -0
  113. package/dist/litellm/model-locate.d.ts.map +1 -0
  114. package/dist/litellm/model-operations.d.ts +59 -0
  115. package/dist/litellm/model-operations.d.ts.map +1 -0
  116. package/dist/litellm/model-types.d.ts +91 -0
  117. package/dist/litellm/model-types.d.ts.map +1 -0
  118. package/dist/litellm/model.d.ts +88 -0
  119. package/dist/litellm/model.d.ts.map +1 -0
  120. package/dist/litellm/operations.d.ts +22 -0
  121. package/dist/litellm/operations.d.ts.map +1 -1
  122. package/dist/litellm/policy-attachment-form.d.ts +19 -0
  123. package/dist/litellm/policy-attachment-form.d.ts.map +1 -0
  124. package/dist/litellm/policy-attachment-operations.d.ts +31 -0
  125. package/dist/litellm/policy-attachment-operations.d.ts.map +1 -0
  126. package/dist/litellm/policy-attachment-types.d.ts +48 -0
  127. package/dist/litellm/policy-attachment-types.d.ts.map +1 -0
  128. package/dist/litellm/policy-attachment.d.ts +61 -0
  129. package/dist/litellm/policy-attachment.d.ts.map +1 -0
  130. package/dist/litellm/policy-form.d.ts +22 -0
  131. package/dist/litellm/policy-form.d.ts.map +1 -0
  132. package/dist/litellm/policy-operations.d.ts +42 -0
  133. package/dist/litellm/policy-operations.d.ts.map +1 -0
  134. package/dist/litellm/policy-types.d.ts +57 -0
  135. package/dist/litellm/policy-types.d.ts.map +1 -0
  136. package/dist/litellm/policy.d.ts +65 -0
  137. package/dist/litellm/policy.d.ts.map +1 -0
  138. package/dist/litellm/providers.d.ts +1 -1
  139. package/dist/litellm/providers.d.ts.map +1 -1
  140. package/dist/litellm/registry-errors.d.ts +62 -0
  141. package/dist/litellm/registry-errors.d.ts.map +1 -0
  142. package/dist/litellm/registry-support.d.ts +42 -0
  143. package/dist/litellm/registry-support.d.ts.map +1 -0
  144. package/dist/litellm/team-form.d.ts +26 -0
  145. package/dist/litellm/team-form.d.ts.map +1 -0
  146. package/dist/litellm/team-members.d.ts +10 -0
  147. package/dist/litellm/team-members.d.ts.map +1 -0
  148. package/dist/litellm/team-operations.d.ts +38 -0
  149. package/dist/litellm/team-operations.d.ts.map +1 -0
  150. package/dist/litellm/team-permission.d.ts +35 -0
  151. package/dist/litellm/team-permission.d.ts.map +1 -0
  152. package/dist/litellm/team-types.d.ts +101 -0
  153. package/dist/litellm/team-types.d.ts.map +1 -0
  154. package/dist/litellm/team.d.ts +67 -0
  155. package/dist/litellm/team.d.ts.map +1 -0
  156. package/dist/litellm/tool-policy-form.d.ts +21 -0
  157. package/dist/litellm/tool-policy-form.d.ts.map +1 -0
  158. package/dist/litellm/tool-policy-operations.d.ts +30 -0
  159. package/dist/litellm/tool-policy-operations.d.ts.map +1 -0
  160. package/dist/litellm/tool-policy-types.d.ts +46 -0
  161. package/dist/litellm/tool-policy-types.d.ts.map +1 -0
  162. package/dist/litellm/tool-policy.d.ts +62 -0
  163. package/dist/litellm/tool-policy.d.ts.map +1 -0
  164. package/dist/litellm/toolset-form.d.ts +20 -0
  165. package/dist/litellm/toolset-form.d.ts.map +1 -0
  166. package/dist/litellm/toolset-operations.d.ts +39 -0
  167. package/dist/litellm/toolset-operations.d.ts.map +1 -0
  168. package/dist/litellm/toolset-types.d.ts +46 -0
  169. package/dist/litellm/toolset-types.d.ts.map +1 -0
  170. package/dist/litellm/toolset.d.ts +55 -0
  171. package/dist/litellm/toolset.d.ts.map +1 -0
  172. package/dist/openbao/auth-role-form.d.ts +9 -0
  173. package/dist/openbao/auth-role-form.d.ts.map +1 -1
  174. package/dist/openbao/auth-role-wire.d.ts.map +1 -1
  175. package/dist/openbao/fake-engines-roles.d.ts.map +1 -1
  176. package/dist/openbao/index.js +43 -6
  177. package/dist/openbao/index.js.map +5 -5
  178. package/dist/ownership/rows.d.ts +11 -0
  179. package/dist/ownership/rows.d.ts.map +1 -1
  180. package/dist/postgres/connection.d.ts +32 -2
  181. package/dist/postgres/connection.d.ts.map +1 -1
  182. package/dist/postgres/database-sql.d.ts +28 -9
  183. package/dist/postgres/database-sql.d.ts.map +1 -1
  184. package/dist/postgres/database.d.ts +1 -1
  185. package/dist/postgres/database.d.ts.map +1 -1
  186. package/dist/postgres/fake-grants-parse.d.ts +37 -0
  187. package/dist/postgres/fake-grants-parse.d.ts.map +1 -0
  188. package/dist/postgres/fake-grants-read.d.ts +61 -0
  189. package/dist/postgres/fake-grants-read.d.ts.map +1 -0
  190. package/dist/postgres/fake-grants-sql.d.ts +46 -0
  191. package/dist/postgres/fake-grants-sql.d.ts.map +1 -0
  192. package/dist/postgres/fake-grants-write.d.ts +29 -0
  193. package/dist/postgres/fake-grants-write.d.ts.map +1 -0
  194. package/dist/postgres/fake-role-sql.d.ts +40 -0
  195. package/dist/postgres/fake-role-sql.d.ts.map +1 -0
  196. package/dist/postgres/fake-schema-sql.d.ts +42 -0
  197. package/dist/postgres/fake-schema-sql.d.ts.map +1 -0
  198. package/dist/postgres/fake-sql-parse.d.ts +23 -0
  199. package/dist/postgres/fake-sql-parse.d.ts.map +1 -0
  200. package/dist/postgres/fake-sql-quote.d.ts +12 -0
  201. package/dist/postgres/fake-sql-quote.d.ts.map +1 -0
  202. package/dist/postgres/fake-sql.d.ts +42 -0
  203. package/dist/postgres/fake-sql.d.ts.map +1 -1
  204. package/dist/postgres/grants-attrs.d.ts +115 -0
  205. package/dist/postgres/grants-attrs.d.ts.map +1 -0
  206. package/dist/postgres/grants-declare.d.ts +88 -0
  207. package/dist/postgres/grants-declare.d.ts.map +1 -0
  208. package/dist/postgres/grants-diff.d.ts +25 -0
  209. package/dist/postgres/grants-diff.d.ts.map +1 -0
  210. package/dist/postgres/grants-errors.d.ts +137 -0
  211. package/dist/postgres/grants-errors.d.ts.map +1 -0
  212. package/dist/postgres/grants-existence.d.ts +14 -0
  213. package/dist/postgres/grants-existence.d.ts.map +1 -0
  214. package/dist/postgres/grants-ops.d.ts +65 -0
  215. package/dist/postgres/grants-ops.d.ts.map +1 -0
  216. package/dist/postgres/grants-ownership.d.ts +18 -0
  217. package/dist/postgres/grants-ownership.d.ts.map +1 -0
  218. package/dist/postgres/grants-plan-restore.d.ts +22 -0
  219. package/dist/postgres/grants-plan-restore.d.ts.map +1 -0
  220. package/dist/postgres/grants-plan.d.ts +65 -0
  221. package/dist/postgres/grants-plan.d.ts.map +1 -0
  222. package/dist/postgres/grants-read-defaults.d.ts +17 -0
  223. package/dist/postgres/grants-read-defaults.d.ts.map +1 -0
  224. package/dist/postgres/grants-read.d.ts +77 -0
  225. package/dist/postgres/grants-read.d.ts.map +1 -0
  226. package/dist/postgres/grants-refuse.d.ts +25 -0
  227. package/dist/postgres/grants-refuse.d.ts.map +1 -0
  228. package/dist/postgres/grants-sql.d.ts +42 -0
  229. package/dist/postgres/grants-sql.d.ts.map +1 -0
  230. package/dist/postgres/grants-words.d.ts +29 -0
  231. package/dist/postgres/grants-words.d.ts.map +1 -0
  232. package/dist/postgres/grants.d.ts +37 -0
  233. package/dist/postgres/grants.d.ts.map +1 -0
  234. package/dist/postgres/index.d.ts +35 -8
  235. package/dist/postgres/index.d.ts.map +1 -1
  236. package/dist/postgres/index.js +2063 -39
  237. package/dist/postgres/index.js.map +47 -7
  238. package/dist/postgres/installed-classifier.d.ts +13 -0
  239. package/dist/postgres/installed-classifier.d.ts.map +1 -0
  240. package/dist/postgres/providers.d.ts +17 -7
  241. package/dist/postgres/providers.d.ts.map +1 -1
  242. package/dist/postgres/psql-executor.d.ts +25 -0
  243. package/dist/postgres/psql-executor.d.ts.map +1 -0
  244. package/dist/postgres/role-attrs.d.ts +109 -0
  245. package/dist/postgres/role-attrs.d.ts.map +1 -0
  246. package/dist/postgres/role-diff.d.ts +22 -0
  247. package/dist/postgres/role-diff.d.ts.map +1 -0
  248. package/dist/postgres/role-errors.d.ts +126 -0
  249. package/dist/postgres/role-errors.d.ts.map +1 -0
  250. package/dist/postgres/role-membership-sql.d.ts +75 -0
  251. package/dist/postgres/role-membership-sql.d.ts.map +1 -0
  252. package/dist/postgres/role-provider.d.ts +11 -0
  253. package/dist/postgres/role-provider.d.ts.map +1 -0
  254. package/dist/postgres/role-scram.d.ts +6 -0
  255. package/dist/postgres/role-scram.d.ts.map +1 -0
  256. package/dist/postgres/role-secrets.d.ts +30 -0
  257. package/dist/postgres/role-secrets.d.ts.map +1 -0
  258. package/dist/postgres/role-sql.d.ts +68 -0
  259. package/dist/postgres/role-sql.d.ts.map +1 -0
  260. package/dist/postgres/role.d.ts +57 -0
  261. package/dist/postgres/role.d.ts.map +1 -0
  262. package/dist/postgres/schema-assert.d.ts +50 -0
  263. package/dist/postgres/schema-assert.d.ts.map +1 -0
  264. package/dist/postgres/schema-attrs.d.ts +51 -0
  265. package/dist/postgres/schema-attrs.d.ts.map +1 -0
  266. package/dist/postgres/schema-delete.d.ts +28 -0
  267. package/dist/postgres/schema-delete.d.ts.map +1 -0
  268. package/dist/postgres/schema-diff.d.ts +14 -0
  269. package/dist/postgres/schema-diff.d.ts.map +1 -0
  270. package/dist/postgres/schema-errors.d.ts +139 -0
  271. package/dist/postgres/schema-errors.d.ts.map +1 -0
  272. package/dist/postgres/schema-identity.d.ts +8 -0
  273. package/dist/postgres/schema-identity.d.ts.map +1 -0
  274. package/dist/postgres/schema-reconcile.d.ts +12 -0
  275. package/dist/postgres/schema-reconcile.d.ts.map +1 -0
  276. package/dist/postgres/schema-sql.d.ts +45 -0
  277. package/dist/postgres/schema-sql.d.ts.map +1 -0
  278. package/dist/postgres/schema-test-kit.d.ts +67 -0
  279. package/dist/postgres/schema-test-kit.d.ts.map +1 -0
  280. package/dist/postgres/schema.d.ts +54 -0
  281. package/dist/postgres/schema.d.ts.map +1 -0
  282. package/dist/proxmox/ceph-flag-types.d.ts +57 -0
  283. package/dist/proxmox/ceph-flag-types.d.ts.map +1 -0
  284. package/dist/proxmox/ceph-flag-wire.d.ts +77 -0
  285. package/dist/proxmox/ceph-flag-wire.d.ts.map +1 -0
  286. package/dist/proxmox/ceph-flag.d.ts +15 -50
  287. package/dist/proxmox/ceph-flag.d.ts.map +1 -1
  288. package/dist/proxmox/ceph-osd-write.d.ts +10 -13
  289. package/dist/proxmox/ceph-osd-write.d.ts.map +1 -1
  290. package/dist/proxmox/index.js +806 -740
  291. package/dist/proxmox/index.js.map +9 -8
  292. package/dist/release/fake-engine.d.ts +10 -0
  293. package/dist/release/fake-engine.d.ts.map +1 -1
  294. package/dist/release/index.js +35 -1
  295. package/dist/release/index.js.map +3 -3
  296. package/dist/valkey/acl-attrs.d.ts +94 -0
  297. package/dist/valkey/acl-attrs.d.ts.map +1 -0
  298. package/dist/valkey/acl-compare.d.ts +28 -0
  299. package/dist/valkey/acl-compare.d.ts.map +1 -0
  300. package/dist/valkey/acl-connection.d.ts +6 -0
  301. package/dist/valkey/acl-connection.d.ts.map +1 -0
  302. package/dist/valkey/acl-form.d.ts +62 -0
  303. package/dist/valkey/acl-form.d.ts.map +1 -0
  304. package/dist/valkey/acl-managed.d.ts +4 -0
  305. package/dist/valkey/acl-managed.d.ts.map +1 -0
  306. package/dist/valkey/acl-ops.d.ts +37 -0
  307. package/dist/valkey/acl-ops.d.ts.map +1 -0
  308. package/dist/valkey/acl-profiles.d.ts +53 -0
  309. package/dist/valkey/acl-profiles.d.ts.map +1 -0
  310. package/dist/valkey/acl-reconcile.d.ts +26 -0
  311. package/dist/valkey/acl-reconcile.d.ts.map +1 -0
  312. package/dist/valkey/acl.d.ts +24 -0
  313. package/dist/valkey/acl.d.ts.map +1 -0
  314. package/dist/valkey/connection-service.d.ts +22 -0
  315. package/dist/valkey/connection-service.d.ts.map +1 -0
  316. package/dist/valkey/connection.d.ts +59 -0
  317. package/dist/valkey/connection.d.ts.map +1 -0
  318. package/dist/valkey/errors.d.ts +165 -0
  319. package/dist/valkey/errors.d.ts.map +1 -0
  320. package/dist/valkey/fake-acl.d.ts +29 -0
  321. package/dist/valkey/fake-acl.d.ts.map +1 -0
  322. package/dist/valkey/fake-valkey.d.ts +35 -0
  323. package/dist/valkey/fake-valkey.d.ts.map +1 -0
  324. package/dist/valkey/handler-test-fixture.d.ts +20 -0
  325. package/dist/valkey/handler-test-fixture.d.ts.map +1 -0
  326. package/dist/valkey/index.d.ts +18 -0
  327. package/dist/valkey/index.d.ts.map +1 -0
  328. package/dist/valkey/index.js +964 -0
  329. package/dist/valkey/index.js.map +27 -0
  330. package/dist/valkey/instance-attrs.d.ts +46 -0
  331. package/dist/valkey/instance-attrs.d.ts.map +1 -0
  332. package/dist/valkey/instance-form.d.ts +22 -0
  333. package/dist/valkey/instance-form.d.ts.map +1 -0
  334. package/dist/valkey/instance.d.ts +48 -0
  335. package/dist/valkey/instance.d.ts.map +1 -0
  336. package/dist/valkey/provider-socket-fixture.d.ts +8 -0
  337. package/dist/valkey/provider-socket-fixture.d.ts.map +1 -0
  338. package/dist/valkey/providers.d.ts +16 -0
  339. package/dist/valkey/providers.d.ts.map +1 -0
  340. package/dist/valkey/resp.d.ts +38 -0
  341. package/dist/valkey/resp.d.ts.map +1 -0
  342. package/dist/valkey/transport-error.d.ts +36 -0
  343. package/dist/valkey/transport-error.d.ts.map +1 -0
  344. package/dist/valkey/transport.d.ts +50 -0
  345. package/dist/valkey/transport.d.ts.map +1 -0
  346. package/docs/litellm-access-group.md +105 -0
  347. package/docs/litellm-budget.md +55 -0
  348. package/docs/litellm-credential.md +60 -0
  349. package/docs/litellm-key-metadata.md +56 -0
  350. package/docs/litellm-key.md +194 -0
  351. package/docs/litellm-mcp.md +187 -0
  352. package/docs/litellm-model.md +111 -0
  353. package/docs/litellm-policy-attachment.md +86 -0
  354. package/docs/litellm-policy.md +103 -0
  355. package/docs/litellm-team.md +117 -0
  356. package/docs/litellm-tool-policy.md +90 -0
  357. package/docs/litellm-toolset.md +92 -0
  358. package/docs/litellm.md +16 -8
  359. package/docs/peers.md +15 -6
  360. package/docs/postgres-grants-example.md +55 -0
  361. package/docs/postgres-grants.md +157 -0
  362. package/docs/postgres-role.md +114 -0
  363. package/docs/postgres-schema.md +139 -0
  364. package/docs/postgres.md +39 -0
  365. package/docs/proxmox-sdk-migration.md +45 -0
  366. package/docs/valkey-transport.md +29 -0
  367. package/docs/valkey.md +187 -0
  368. package/package.json +7 -3
  369. package/src/litellm/access-group-form.ts +105 -0
  370. package/src/litellm/access-group-operations.ts +92 -0
  371. package/src/litellm/access-group-types.ts +49 -0
  372. package/src/litellm/access-group.test.ts +197 -0
  373. package/src/litellm/access-group.ts +151 -0
  374. package/src/litellm/budget-errors.ts +38 -0
  375. package/src/litellm/budget-form.ts +94 -0
  376. package/src/litellm/budget-operations.ts +51 -0
  377. package/src/litellm/budget.test.ts +105 -0
  378. package/src/litellm/budget.ts +127 -0
  379. package/src/litellm/credential-errors.ts +183 -0
  380. package/src/litellm/credential-form.test.ts +151 -0
  381. package/src/litellm/credential-form.ts +211 -0
  382. package/src/litellm/credential-guards.test.ts +241 -0
  383. package/src/litellm/credential-operations.ts +208 -0
  384. package/src/litellm/credential-recovery.test.ts +93 -0
  385. package/src/litellm/credential-rewrite.test.ts +164 -0
  386. package/src/litellm/credential-rewrite.ts +50 -0
  387. package/src/litellm/credential-types.ts +117 -0
  388. package/src/litellm/credential-values.ts +94 -0
  389. package/src/litellm/credential.test.ts +218 -0
  390. package/src/litellm/credential.ts +227 -0
  391. package/src/litellm/fake-access-group-litellm.ts +127 -0
  392. package/src/litellm/fake-credential-litellm.ts +216 -0
  393. package/src/litellm/fake-keys.ts +190 -0
  394. package/src/litellm/fake-litellm.ts +66 -7
  395. package/src/litellm/fake-mcp-litellm.ts +214 -0
  396. package/src/litellm/fake-model-litellm-post-update.ts +103 -0
  397. package/src/litellm/fake-model-litellm.ts +233 -0
  398. package/src/litellm/fake-policy-attachment-litellm.ts +97 -0
  399. package/src/litellm/fake-policy-litellm.ts +186 -0
  400. package/src/litellm/fake-registry-base.ts +69 -0
  401. package/src/litellm/fake-stack.ts +17 -1
  402. package/src/litellm/fake-team-litellm.ts +229 -0
  403. package/src/litellm/fake-tool-litellm.ts +88 -0
  404. package/src/litellm/fake-toolset-litellm.ts +110 -0
  405. package/src/litellm/index.ts +153 -0
  406. package/src/litellm/key-delete.test.ts +212 -0
  407. package/src/litellm/key-errors.ts +241 -0
  408. package/src/litellm/key-form.ts +200 -0
  409. package/src/litellm/key-harness.ts +121 -0
  410. package/src/litellm/key-metadata-errors.ts +49 -0
  411. package/src/litellm/key-metadata.test.ts +156 -0
  412. package/src/litellm/key-metadata.ts +74 -0
  413. package/src/litellm/key-operations.ts +184 -0
  414. package/src/litellm/key-rotation.test.ts +110 -0
  415. package/src/litellm/key-scope.test.ts +135 -0
  416. package/src/litellm/key-secret.test.ts +248 -0
  417. package/src/litellm/key-secret.ts +137 -0
  418. package/src/litellm/key-transport.test.ts +166 -0
  419. package/src/litellm/key.test.ts +231 -0
  420. package/src/litellm/key.ts +213 -0
  421. package/src/litellm/mcp-server-adopt.test.ts +86 -0
  422. package/src/litellm/mcp-server-alias.test.ts +184 -0
  423. package/src/litellm/mcp-server-credential.ts +85 -0
  424. package/src/litellm/mcp-server-errors.ts +141 -0
  425. package/src/litellm/mcp-server-exports.test.ts +15 -0
  426. package/src/litellm/mcp-server-form.test.ts +241 -0
  427. package/src/litellm/mcp-server-form.ts +233 -0
  428. package/src/litellm/mcp-server-handlers.test.ts +150 -0
  429. package/src/litellm/mcp-server-locate.ts +66 -0
  430. package/src/litellm/mcp-server-operations.ts +111 -0
  431. package/src/litellm/mcp-server-registry.test.ts +146 -0
  432. package/src/litellm/mcp-server-retype.test.ts +114 -0
  433. package/src/litellm/mcp-server-shadow.test.ts +91 -0
  434. package/src/litellm/mcp-server-shadow.ts +45 -0
  435. package/src/litellm/mcp-server-tools.test.ts +85 -0
  436. package/src/litellm/mcp-server-types.ts +143 -0
  437. package/src/litellm/mcp-server-url.ts +66 -0
  438. package/src/litellm/mcp-server.test.ts +213 -0
  439. package/src/litellm/mcp-server.ts +225 -0
  440. package/src/litellm/model-claim.ts +69 -0
  441. package/src/litellm/model-credential.ts +45 -0
  442. package/src/litellm/model-errors.ts +168 -0
  443. package/src/litellm/model-foreign-row.test.ts +173 -0
  444. package/src/litellm/model-form.test.ts +228 -0
  445. package/src/litellm/model-form.ts +223 -0
  446. package/src/litellm/model-locate.test.ts +128 -0
  447. package/src/litellm/model-locate.ts +67 -0
  448. package/src/litellm/model-operations.test.ts +93 -0
  449. package/src/litellm/model-operations.ts +146 -0
  450. package/src/litellm/model-params-preserve.test.ts +119 -0
  451. package/src/litellm/model-recovery.test.ts +57 -0
  452. package/src/litellm/model-types.ts +112 -0
  453. package/src/litellm/model.test.ts +222 -0
  454. package/src/litellm/model.ts +237 -0
  455. package/src/litellm/operations.ts +1 -1
  456. package/src/litellm/policy-attachment-form.ts +103 -0
  457. package/src/litellm/policy-attachment-operations.ts +74 -0
  458. package/src/litellm/policy-attachment-types.ts +48 -0
  459. package/src/litellm/policy-attachment.test.ts +198 -0
  460. package/src/litellm/policy-attachment.ts +178 -0
  461. package/src/litellm/policy-form.ts +122 -0
  462. package/src/litellm/policy-operations.ts +112 -0
  463. package/src/litellm/policy-types.ts +58 -0
  464. package/src/litellm/policy.test.ts +214 -0
  465. package/src/litellm/policy.ts +146 -0
  466. package/src/litellm/providers.ts +25 -1
  467. package/src/litellm/registry-errors.ts +88 -0
  468. package/src/litellm/registry-exports.test.ts +66 -0
  469. package/src/litellm/registry-support.ts +109 -0
  470. package/src/litellm/team-form.ts +140 -0
  471. package/src/litellm/team-members.test.ts +150 -0
  472. package/src/litellm/team-members.ts +58 -0
  473. package/src/litellm/team-operations.ts +90 -0
  474. package/src/litellm/team-permission.ts +159 -0
  475. package/src/litellm/team-read.test.ts +89 -0
  476. package/src/litellm/team-types.ts +106 -0
  477. package/src/litellm/team.test.ts +206 -0
  478. package/src/litellm/team.ts +159 -0
  479. package/src/litellm/tool-policy-form.ts +94 -0
  480. package/src/litellm/tool-policy-operations.ts +58 -0
  481. package/src/litellm/tool-policy-types.ts +48 -0
  482. package/src/litellm/tool-policy.test.ts +172 -0
  483. package/src/litellm/tool-policy.ts +125 -0
  484. package/src/litellm/toolset-form.ts +104 -0
  485. package/src/litellm/toolset-operations.ts +90 -0
  486. package/src/litellm/toolset-types.ts +47 -0
  487. package/src/litellm/toolset.test.ts +198 -0
  488. package/src/litellm/toolset.ts +151 -0
  489. package/src/openbao/auth-role-form.test.ts +37 -0
  490. package/src/openbao/auth-role-form.ts +12 -1
  491. package/src/openbao/auth-role-wire.test.ts +1 -0
  492. package/src/openbao/auth-role-wire.ts +13 -1
  493. package/src/openbao/distilled-retry.test.ts +1 -0
  494. package/src/openbao/fake-engines-roles.ts +7 -1
  495. package/src/ownership/ownership.test.ts +33 -1
  496. package/src/ownership/rows.ts +53 -0
  497. package/src/postgres/connection.ts +72 -3
  498. package/src/postgres/database-bigint-serialization.test.ts +1 -0
  499. package/src/postgres/database-refusals.test.ts +2 -2
  500. package/src/postgres/database-sql.ts +40 -12
  501. package/src/postgres/database.ts +3 -2
  502. package/src/postgres/fake-grants-parse.ts +189 -0
  503. package/src/postgres/fake-grants-read.ts +250 -0
  504. package/src/postgres/fake-grants-sql.ts +207 -0
  505. package/src/postgres/fake-grants-write.ts +90 -0
  506. package/src/postgres/fake-role-sql.ts +220 -0
  507. package/src/postgres/fake-schema-semantics.test.ts +36 -0
  508. package/src/postgres/fake-schema-sql.ts +146 -0
  509. package/src/postgres/fake-sql-parse.ts +86 -0
  510. package/src/postgres/fake-sql-quote.ts +36 -0
  511. package/src/postgres/fake-sql.ts +161 -46
  512. package/src/postgres/fixtures/pg-REL_18_6-acl.h.txt +147 -0
  513. package/src/postgres/fixtures/pg-REL_18_6-aclexplode-func.txt +31 -0
  514. package/src/postgres/fixtures/pg-REL_18_6-alter-default-privileges-synopsis.txt +82 -0
  515. package/src/postgres/fixtures/pg-REL_18_6-alter_role.sgml.txt +44 -0
  516. package/src/postgres/fixtures/pg-REL_18_6-create_role.sgml.txt +26 -0
  517. package/src/postgres/fixtures/pg-REL_18_6-grant-synopsis.txt +95 -0
  518. package/src/postgres/fixtures/pg-REL_18_6-pg_attribute.h.txt +24 -0
  519. package/src/postgres/fixtures/pg-REL_18_6-pg_auth_members.h.txt +62 -0
  520. package/src/postgres/fixtures/pg-REL_18_6-pg_authid.h.txt +70 -0
  521. package/src/postgres/fixtures/pg-REL_18_6-pg_class.h.txt +33 -0
  522. package/src/postgres/fixtures/pg-REL_18_6-pg_default_acl.h.txt +81 -0
  523. package/src/postgres/fixtures/pg-REL_18_6-pg_namespace.h.txt +28 -0
  524. package/src/postgres/fixtures/pg-REL_18_6-revoke-synopsis.txt +124 -0
  525. package/src/postgres/grants-acl.test.ts +61 -0
  526. package/src/postgres/grants-attrs.ts +185 -0
  527. package/src/postgres/grants-database.test.ts +158 -0
  528. package/src/postgres/grants-declare.ts +172 -0
  529. package/src/postgres/grants-delete-proof.test.ts +154 -0
  530. package/src/postgres/grants-diff.ts +101 -0
  531. package/src/postgres/grants-errors.ts +215 -0
  532. package/src/postgres/grants-existence-guards.test.ts +113 -0
  533. package/src/postgres/grants-existence.ts +101 -0
  534. package/src/postgres/grants-lifecycle-guards.test.ts +208 -0
  535. package/src/postgres/grants-lifecycle-repair.test.ts +208 -0
  536. package/src/postgres/grants-lifecycle.test.ts +181 -0
  537. package/src/postgres/grants-ops.ts +229 -0
  538. package/src/postgres/grants-owner-revoke.test.ts +121 -0
  539. package/src/postgres/grants-ownership.ts +49 -0
  540. package/src/postgres/grants-plan-collateral.test.ts +202 -0
  541. package/src/postgres/grants-plan-diff.test.ts +204 -0
  542. package/src/postgres/grants-plan-restore.ts +43 -0
  543. package/src/postgres/grants-plan-revocations.test.ts +69 -0
  544. package/src/postgres/grants-plan.test.ts +148 -0
  545. package/src/postgres/grants-plan.ts +245 -0
  546. package/src/postgres/grants-provenance.test.ts +134 -0
  547. package/src/postgres/grants-psql-lifecycle.test.ts +105 -0
  548. package/src/postgres/grants-read-absent.test.ts +91 -0
  549. package/src/postgres/grants-read-defaults.ts +51 -0
  550. package/src/postgres/grants-read.ts +250 -0
  551. package/src/postgres/grants-refusals.test.ts +136 -0
  552. package/src/postgres/grants-refuse.ts +119 -0
  553. package/src/postgres/grants-relkind-wire.test.ts +111 -0
  554. package/src/postgres/grants-repair-plan.test.ts +201 -0
  555. package/src/postgres/grants-sql.test.ts +128 -0
  556. package/src/postgres/grants-sql.ts +144 -0
  557. package/src/postgres/grants-words.ts +34 -0
  558. package/src/postgres/grants.ts +110 -0
  559. package/src/postgres/index.ts +137 -7
  560. package/src/postgres/installed-classifier.ts +36 -0
  561. package/src/postgres/providers.ts +44 -7
  562. package/src/postgres/psql-executor-escaping.test.ts +67 -0
  563. package/src/postgres/psql-executor.test.ts +167 -0
  564. package/src/postgres/psql-executor.ts +170 -0
  565. package/src/postgres/role-adopt.test.ts +98 -0
  566. package/src/postgres/role-attrs.ts +134 -0
  567. package/src/postgres/role-diff.ts +89 -0
  568. package/src/postgres/role-errors.ts +198 -0
  569. package/src/postgres/role-guard.test.ts +60 -0
  570. package/src/postgres/role-identity.test.ts +185 -0
  571. package/src/postgres/role-lifecycle.test.ts +228 -0
  572. package/src/postgres/role-membership-sql.ts +198 -0
  573. package/src/postgres/role-password.test.ts +158 -0
  574. package/src/postgres/role-privilege.test.ts +80 -0
  575. package/src/postgres/role-provenance.test.ts +200 -0
  576. package/src/postgres/role-provider.ts +104 -0
  577. package/src/postgres/role-refusals.test.ts +250 -0
  578. package/src/postgres/role-scram.test.ts +38 -0
  579. package/src/postgres/role-scram.ts +38 -0
  580. package/src/postgres/role-secrets.ts +46 -0
  581. package/src/postgres/role-sql.ts +211 -0
  582. package/src/postgres/role-valid-until.test.ts +68 -0
  583. package/src/postgres/role.ts +218 -0
  584. package/src/postgres/schema-assert.ts +139 -0
  585. package/src/postgres/schema-attrs.ts +71 -0
  586. package/src/postgres/schema-delete-proofs.test.ts +192 -0
  587. package/src/postgres/schema-delete.ts +96 -0
  588. package/src/postgres/schema-diff.ts +44 -0
  589. package/src/postgres/schema-drop-2bp01.test.ts +146 -0
  590. package/src/postgres/schema-errors.ts +209 -0
  591. package/src/postgres/schema-handlers.test.ts +193 -0
  592. package/src/postgres/schema-identity.ts +39 -0
  593. package/src/postgres/schema-lifecycle.test.ts +246 -0
  594. package/src/postgres/schema-race.test.ts +70 -0
  595. package/src/postgres/schema-reconcile.ts +100 -0
  596. package/src/postgres/schema-refusals.test.ts +144 -0
  597. package/src/postgres/schema-socket.test.ts +161 -0
  598. package/src/postgres/schema-sql.ts +130 -0
  599. package/src/postgres/schema-takeover.test.ts +149 -0
  600. package/src/postgres/schema-test-kit.ts +85 -0
  601. package/src/postgres/schema.ts +130 -0
  602. package/src/proxmox/ceph-flag-types.ts +70 -0
  603. package/src/proxmox/ceph-flag-wire.test.ts +72 -0
  604. package/src/proxmox/ceph-flag-wire.ts +123 -0
  605. package/src/proxmox/ceph-flag.ts +77 -130
  606. package/src/proxmox/ceph-osd-write.ts +10 -13
  607. package/src/release/binary-alias.test.ts +3 -3
  608. package/src/release/binary-claims.test.ts +3 -3
  609. package/src/release/binary-directory-umask.test.ts +65 -0
  610. package/src/release/fake-engine.ts +15 -0
  611. package/src/valkey/acl-attrs.ts +98 -0
  612. package/src/valkey/acl-compare.ts +57 -0
  613. package/src/valkey/acl-connection.ts +13 -0
  614. package/src/valkey/acl-delete.test.ts +127 -0
  615. package/src/valkey/acl-form.ts +239 -0
  616. package/src/valkey/acl-managed.ts +13 -0
  617. package/src/valkey/acl-ops.ts +122 -0
  618. package/src/valkey/acl-ownership.test.ts +118 -0
  619. package/src/valkey/acl-prefix.test.ts +38 -0
  620. package/src/valkey/acl-profiles.ts +81 -0
  621. package/src/valkey/acl-reconcile.ts +180 -0
  622. package/src/valkey/acl-reserved.test.ts +69 -0
  623. package/src/valkey/acl-revocation.test.ts +94 -0
  624. package/src/valkey/acl-seat-prefix.test.ts +111 -0
  625. package/src/valkey/acl-validation.test.ts +54 -0
  626. package/src/valkey/acl.test.ts +215 -0
  627. package/src/valkey/acl.ts +169 -0
  628. package/src/valkey/connection-service.ts +46 -0
  629. package/src/valkey/connection.ts +180 -0
  630. package/src/valkey/errors.ts +239 -0
  631. package/src/valkey/fake-acl.ts +128 -0
  632. package/src/valkey/fake-valkey.ts +224 -0
  633. package/src/valkey/handler-test-fixture.ts +43 -0
  634. package/src/valkey/handlers.test.ts +205 -0
  635. package/src/valkey/index.ts +54 -0
  636. package/src/valkey/instance-attrs.ts +48 -0
  637. package/src/valkey/instance-form.ts +104 -0
  638. package/src/valkey/instance.test.ts +108 -0
  639. package/src/valkey/instance.ts +160 -0
  640. package/src/valkey/provider-socket-fixture.ts +71 -0
  641. package/src/valkey/providers.test.ts +129 -0
  642. package/src/valkey/providers.ts +21 -0
  643. package/src/valkey/resp.ts +212 -0
  644. package/src/valkey/transport-bounds.test.ts +150 -0
  645. package/src/valkey/transport-error.ts +37 -0
  646. package/src/valkey/transport.test.ts +158 -0
  647. package/src/valkey/transport.ts +109 -0
  648. package/src/valkey/upstream-bounds.test.ts +14 -0
@@ -1,30 +1,26 @@
1
1
  // src/postgres/connection.ts
2
2
  import * as PgClient from "@effect/sql-pg/PgClient";
3
3
  import * as Context from "effect/Context";
4
- import * as Effect from "effect/Effect";
4
+ import * as Effect3 from "effect/Effect";
5
5
  import * as Layer from "effect/Layer";
6
6
 
7
- class PostgresConnection extends Context.Service()("Postgres.Connection") {
8
- }
9
- var postgresConnection = (config) => Layer.succeed(PostgresConnection, Effect.succeed(config));
10
- var withPg = (build) => Effect.gen(function* () {
11
- const resolveConfig = yield* PostgresConnection;
12
- const config = yield* resolveConfig;
13
- return yield* Effect.provide(Effect.flatMap(PgClient.PgClient, build), PgClient.layer(config));
14
- });
15
- // src/postgres/database-attrs.ts
16
- var POSTGRES_NAME_MAX_BYTES = 63;
17
- var utf8ByteLength = (value) => new TextEncoder().encode(value).byteLength;
18
- var nameByteRefusal = (name) => {
19
- const byteLength = utf8ByteLength(name);
20
- return byteLength > POSTGRES_NAME_MAX_BYTES ? { byteLength, limit: POSTGRES_NAME_MAX_BYTES } : undefined;
21
- };
22
- // src/postgres/database-sql.ts
7
+ // src/postgres/psql-executor.ts
23
8
  import * as Effect2 from "effect/Effect";
9
+ import {
10
+ ConnectionError,
11
+ SqlError,
12
+ SqlSyntaxError,
13
+ UnknownError
14
+ } from "effect/unstable/sql/SqlError";
15
+
16
+ // src/postgres/database-sql.ts
17
+ import * as Effect from "effect/Effect";
24
18
  var quoteIdent = (value) => `"${value.replace(/"/g, '""')}"`;
25
- var quoteStringLiteral = (value) => `'${value.replace(/'/g, "''")}'`;
26
- var withOptions = (props) => {
19
+ var quoteStringLiteral = (value) => `E'${value.replace(/\\/g, "\\\\").replace(/'/g, "''")}'`;
20
+ var withOptions = (props, template) => {
27
21
  const parts = [`OWNER ${quoteIdent(props.owner)}`];
22
+ if (template !== undefined)
23
+ parts.push(`TEMPLATE ${quoteIdent(template)}`);
28
24
  if (props.encoding !== undefined)
29
25
  parts.push(`ENCODING ${quoteStringLiteral(props.encoding)}`);
30
26
  if (props.localeProvider !== undefined) {
@@ -46,7 +42,7 @@ var withOptions = (props) => {
46
42
  parts.push(`IS_TEMPLATE ${props.isTemplate ? "true" : "false"}`);
47
43
  return parts.join(" ");
48
44
  };
49
- var buildCreateDatabaseSql = (props) => `CREATE DATABASE ${quoteIdent(props.name)} WITH ${withOptions(props)}`;
45
+ var buildCreateDatabaseSql = (props, template) => `CREATE DATABASE ${quoteIdent(props.name)} WITH ${withOptions(props, template)}`;
50
46
  var isDuplicateDatabaseRace = (error) => {
51
47
  if (error.reason._tag !== "SqlSyntaxError")
52
48
  return false;
@@ -54,7 +50,9 @@ var isDuplicateDatabaseRace = (error) => {
54
50
  return typeof cause === "object" && cause !== null && cause.code === "42P04";
55
51
  };
56
52
  var ROLE_EXISTS_SQL = "SELECT 1 AS present FROM pg_roles WHERE rolname = $1";
57
- var roleExists = (pg, role) => Effect2.map(pg.unsafe(ROLE_EXISTS_SQL, [role]), (rows) => rows.length > 0);
53
+ var roleExists = (pg, role) => Effect.map(pg.unsafe(ROLE_EXISTS_SQL, [role]), (rows) => rows.length > 0);
54
+ var DATABASE_EXISTS_SQL = "SELECT 1 AS present FROM pg_database WHERE datname = $1";
55
+ var databaseExists = (pg, name) => Effect.map(pg.unsafe(DATABASE_EXISTS_SQL, [name]), (rows) => rows.length > 0);
58
56
  var SELECT_DATABASE_SQL = `SELECT
59
57
  d.oid AS oid,
60
58
  d.datname AS name,
@@ -73,7 +71,7 @@ var SELECT_DATABASE_SQL = `SELECT
73
71
  FROM pg_database d
74
72
  JOIN pg_tablespace t ON t.oid = d.dattablespace
75
73
  WHERE d.datname = $1`;
76
- var selectDatabase = (pg, name) => Effect2.map(pg.unsafe(SELECT_DATABASE_SQL, [name]), (rows) => rows[0]);
74
+ var selectDatabase = (pg, name) => Effect.map(pg.unsafe(SELECT_DATABASE_SQL, [name]), (rows) => rows[0]);
77
75
  var firstDrift = (props, live) => {
78
76
  const checks = [
79
77
  ["encoding", props.encoding, live.encoding],
@@ -91,12 +89,121 @@ var firstDrift = (props, live) => {
91
89
  }
92
90
  return;
93
91
  };
92
+
93
+ // src/postgres/psql-executor.ts
94
+ var inlineParams = (sql, params) => params.length === 0 ? sql : sql.replace(/\$(\d+)/g, (_match, index) => {
95
+ const value = params[Number(index) - 1];
96
+ if (typeof value !== "string") {
97
+ throw new Error(`psql executor: param $${index} must be a string, got ${typeof value}`);
98
+ }
99
+ return quoteStringLiteral(value);
100
+ });
101
+ var normalizeRow = (row) => typeof row === "object" && row !== null && typeof row.oid === "string" ? { ...row, oid: Number(row.oid) } : row;
102
+ var returnsRows = (sql) => /^\s*(SELECT|WITH)\b/i.test(sql);
103
+ var wrapRows = (sql) => `SELECT coalesce(json_agg(t), '[]'::json)::text FROM (${sql}) t;`;
104
+ var redactPasswordLiterals = (text) => text.replace(/PASSWORD\s+(?:E'(?:[^'\\]|\\[\s\S]|'')*'|'(?:[^']|'')*')/gi, "PASSWORD '[redacted]'");
105
+ var failure = (operation, result) => {
106
+ const state = /ERROR:\s+([0-9A-Z]{5}):/.exec(result.stderr)?.[1];
107
+ const stderr = redactPasswordLiterals(result.stderr.trim());
108
+ const fields = {
109
+ cause: Object.assign(new Error(stderr), state === undefined ? {} : { code: state }),
110
+ message: stderr,
111
+ operation: redactPasswordLiterals(operation)
112
+ };
113
+ const reason = state === undefined ? new ConnectionError(fields) : state.startsWith("42") ? new SqlSyntaxError(fields) : new UnknownError(fields);
114
+ return new SqlError({ reason });
115
+ };
116
+ var runScript = (run, target, stdin, operation) => Effect2.gen(function* () {
117
+ const result = yield* Effect2.tryPromise({
118
+ try: () => run({
119
+ argv: [
120
+ "psql",
121
+ "-X",
122
+ "-q",
123
+ "-A",
124
+ "-t",
125
+ "-v",
126
+ "ON_ERROR_STOP=1",
127
+ "-v",
128
+ "VERBOSITY=verbose",
129
+ "-U",
130
+ target.username,
131
+ "-d",
132
+ target.database
133
+ ],
134
+ stdin
135
+ }),
136
+ catch: (cause) => new SqlError({ reason: new ConnectionError({ cause, operation: "psql exec" }) })
137
+ });
138
+ if (result.code !== 0) {
139
+ return yield* Effect2.fail(failure(operation, result));
140
+ }
141
+ return result;
142
+ });
143
+ var makePsqlExecutor = (run, target) => ({
144
+ unsafe: (sql, params = []) => Effect2.gen(function* () {
145
+ const inlined = yield* Effect2.try({
146
+ try: () => inlineParams(sql, params),
147
+ catch: (cause) => new SqlError({ reason: new UnknownError({ cause, operation: "psql inline params" }) })
148
+ });
149
+ const rows = returnsRows(inlined);
150
+ const result = yield* runScript(run, target, rows ? wrapRows(inlined) : `${inlined};`, redactPasswordLiterals(inlined).slice(0, 40));
151
+ if (!rows)
152
+ return [];
153
+ const parsed = yield* Effect2.try({
154
+ try: () => JSON.parse(result.stdout.trim() || "[]").map(normalizeRow),
155
+ catch: (cause) => new SqlError({ reason: new UnknownError({ cause, operation: "psql parse output" }) })
156
+ });
157
+ return parsed;
158
+ }),
159
+ transaction: (statements) => Effect2.gen(function* () {
160
+ const script = ["BEGIN", ...statements, "COMMIT"].join(`;
161
+ `);
162
+ yield* runScript(run, target, `${script};`, "BEGIN");
163
+ })
164
+ });
165
+
166
+ // src/postgres/connection.ts
167
+ class PostgresConnection extends Context.Service()("Postgres.Connection") {
168
+ }
169
+ var postgresConnection = (config) => Layer.succeed(PostgresConnection, Effect3.succeed(config));
170
+ var postgresRunnerConnection = (config) => Layer.succeed(PostgresConnection, Effect3.succeed(config));
171
+ var PostgresPool = Context.Reference("homeflare/postgres/Pool", {
172
+ defaultValue: () => PgClient.layer
173
+ });
174
+ var isRunner = (c) => ("run" in c);
175
+ var withPg = (build, database) => Effect3.gen(function* () {
176
+ const resolveConfig = yield* PostgresConnection;
177
+ const config = yield* resolveConfig;
178
+ const target = database === undefined ? config : { ...config, database };
179
+ if (isRunner(target)) {
180
+ return yield* build(makePsqlExecutor(target.run, target), {
181
+ template: target.template ?? "template0",
182
+ database: target.database
183
+ });
184
+ }
185
+ const pool = yield* PostgresPool;
186
+ return yield* Effect3.provide(Effect3.flatMap(PgClient.PgClient, (pg) => build({
187
+ unsafe: (sql, params) => pg.unsafe(sql, params),
188
+ transaction: (statements) => pg.withTransaction(Effect3.gen(function* () {
189
+ for (const sql of statements)
190
+ yield* pg.unsafe(sql).pipe(Effect3.asVoid);
191
+ }))
192
+ }, { database: target.database })), pool(target));
193
+ });
194
+ // src/postgres/database-attrs.ts
195
+ var POSTGRES_NAME_MAX_BYTES = 63;
196
+ var utf8ByteLength = (value) => new TextEncoder().encode(value).byteLength;
197
+ var nameByteRefusal = (name) => {
198
+ const byteLength = utf8ByteLength(name);
199
+ return byteLength > POSTGRES_NAME_MAX_BYTES ? { byteLength, limit: POSTGRES_NAME_MAX_BYTES } : undefined;
200
+ };
94
201
  // src/postgres/database.ts
95
202
  import { Resource } from "alchemy";
96
203
  import { Unowned } from "alchemy/AdoptPolicy";
97
204
  import { isResolved } from "alchemy/Diff";
98
205
  import * as Provider from "alchemy/Provider";
99
- import * as Effect3 from "effect/Effect";
206
+ import * as Effect4 from "effect/Effect";
100
207
 
101
208
  // src/postgres/errors.ts
102
209
  import * as Data from "effect/Data";
@@ -148,23 +255,23 @@ var liveDrift = (props, live) => {
148
255
  }
149
256
  return firstDrift(props, live);
150
257
  };
151
- var reconcileWithClient = (pg, props) => Effect3.gen(function* () {
258
+ var reconcileWithClient = (pg, props, template) => Effect4.gen(function* () {
152
259
  const observed = yield* selectDatabase(pg, props.name);
153
260
  if (observed === undefined) {
154
261
  const ownerPresent = yield* roleExists(pg, props.owner);
155
262
  if (!ownerPresent) {
156
- return yield* Effect3.fail(new PostgresDatabaseOwnerMissing({ database: props.name, owner: props.owner }));
263
+ return yield* Effect4.fail(new PostgresDatabaseOwnerMissing({ database: props.name, owner: props.owner }));
157
264
  }
158
- yield* pg.unsafe(buildCreateDatabaseSql(props)).pipe(Effect3.asVoid, Effect3.catchTag("SqlError", (error) => isDuplicateDatabaseRace(error) ? Effect3.succeed(undefined) : Effect3.fail(error)));
265
+ yield* pg.unsafe(buildCreateDatabaseSql(props, template)).pipe(Effect4.asVoid, Effect4.catchTag("SqlError", (error) => isDuplicateDatabaseRace(error) ? Effect4.succeed(undefined) : Effect4.fail(error)));
159
266
  const created = yield* selectDatabase(pg, props.name);
160
267
  if (created === undefined) {
161
- return yield* Effect3.fail(new PostgresDatabaseCreateVanished({ database: props.name }));
268
+ return yield* Effect4.fail(new PostgresDatabaseCreateVanished({ database: props.name }));
162
269
  }
163
270
  return created;
164
271
  }
165
272
  const drift = liveDrift(props, observed);
166
273
  if (drift !== undefined) {
167
- return yield* Effect3.fail(new PostgresDatabaseDrift({
274
+ return yield* Effect4.fail(new PostgresDatabaseDrift({
168
275
  database: props.name,
169
276
  prop: drift.prop,
170
277
  declared: drift.declared,
@@ -174,42 +281,1871 @@ var reconcileWithClient = (pg, props) => Effect3.gen(function* () {
174
281
  return observed;
175
282
  });
176
283
  var readWithClient = (pg, name) => selectDatabase(pg, name);
177
- var diffPostgresDatabase = (news, output) => Effect3.gen(function* () {
284
+ var diffPostgresDatabase = (news, output) => Effect4.gen(function* () {
178
285
  if (output === undefined || !isResolved(news))
179
286
  return;
180
287
  if (news.name !== output.name) {
181
- return yield* Effect3.fail(new PostgresDatabaseRenameRefused({ from: output.name, to: news.name }));
288
+ return yield* Effect4.fail(new PostgresDatabaseRenameRefused({ from: output.name, to: news.name }));
182
289
  }
183
290
  const nameRefusal = nameByteRefusal(news.name);
184
291
  if (nameRefusal !== undefined) {
185
- return yield* Effect3.fail(new PostgresDatabaseNameRefused({ name: news.name, ...nameRefusal }));
292
+ return yield* Effect4.fail(new PostgresDatabaseNameRefused({ name: news.name, ...nameRefusal }));
186
293
  }
187
294
  const changed = news.owner !== output.owner || firstDrift(news, output) !== undefined;
188
295
  return changed ? { action: "update" } : { action: "noop" };
189
296
  });
190
297
  var postgresDatabaseHandlers = PostgresDatabase.Provider.of({
191
- list: () => Effect3.succeed([]),
298
+ list: () => Effect4.succeed([]),
192
299
  nuke: { skip: true },
193
- read: ({ olds, output }) => Effect3.gen(function* () {
300
+ read: ({ olds, output }) => Effect4.gen(function* () {
194
301
  const name = output?.name ?? olds.name;
195
302
  const live = yield* withPg((pg) => readWithClient(pg, name));
196
303
  return live === undefined ? undefined : Unowned(live);
197
304
  }),
198
305
  diff: ({ news, output }) => diffPostgresDatabase(news, output),
199
- reconcile: ({ news }) => Effect3.gen(function* () {
306
+ reconcile: ({ news }) => Effect4.gen(function* () {
200
307
  const nameRefusal = nameByteRefusal(news.name);
201
308
  if (nameRefusal !== undefined) {
202
- return yield* Effect3.fail(new PostgresDatabaseNameRefused({ name: news.name, ...nameRefusal }));
309
+ return yield* Effect4.fail(new PostgresDatabaseNameRefused({ name: news.name, ...nameRefusal }));
203
310
  }
204
- return yield* withPg((pg) => reconcileWithClient(pg, news));
311
+ return yield* withPg((pg, ctx) => reconcileWithClient(pg, news, ctx.template));
205
312
  }),
206
- delete: ({ olds }) => Effect3.fail(new PostgresDatabaseDropRefused({ database: olds.name }))
313
+ delete: ({ olds }) => Effect4.fail(new PostgresDatabaseDropRefused({ database: olds.name }))
207
314
  });
208
315
  var PostgresDatabaseProvider = () => Provider.succeed(PostgresDatabase, postgresDatabaseHandlers);
316
+ // src/postgres/schema-attrs.ts
317
+ var normalizedComment = (comment) => comment === "" ? undefined : comment;
318
+ var schemaNameByteRefusal = (name) => {
319
+ const byteLength = utf8ByteLength(name);
320
+ return byteLength > POSTGRES_NAME_MAX_BYTES ? { byteLength, limit: POSTGRES_NAME_MAX_BYTES } : undefined;
321
+ };
322
+ // src/postgres/schema-sql.ts
323
+ import * as Effect5 from "effect/Effect";
324
+ var buildCreateSchemaSql = (props) => `CREATE SCHEMA ${quoteIdent(props.name)}${props.owner === undefined ? "" : ` AUTHORIZATION ${quoteIdent(props.owner)}`}`;
325
+ var buildCommentSchemaSql = (name, comment) => `COMMENT ON SCHEMA ${quoteIdent(name)} IS ${quoteStringLiteral(comment)}`;
326
+ var buildDropSchemaSql = (name, cascade) => `DROP SCHEMA IF EXISTS ${quoteIdent(name)}${cascade ? " CASCADE" : ""}`;
327
+ var SELECT_SCHEMA_SQL = `SELECT
328
+ n.oid AS oid,
329
+ n.nspname AS name,
330
+ pg_get_userbyid(n.nspowner) AS owner,
331
+ obj_description(n.oid, 'pg_namespace') AS comment,
332
+ current_database() AS database
333
+ FROM pg_namespace n
334
+ WHERE n.nspname = $1`;
335
+ var selectSchema = (pg, name) => Effect5.map(pg.unsafe(SELECT_SCHEMA_SQL, [name]), (rows) => rows[0]);
336
+ var CURRENT_DATABASE_SQL = "SELECT current_database() AS database";
337
+ var currentDatabase = (pg) => Effect5.map(pg.unsafe(CURRENT_DATABASE_SQL), (rows) => rows[0]?.database ?? "");
338
+ var CURRENT_USER_SQL = "SELECT current_user AS role";
339
+ var currentUser = (pg) => Effect5.map(pg.unsafe(CURRENT_USER_SQL), (rows) => rows[0]?.role ?? "");
340
+ var SCHEMA_EMPTY_SQL = `WITH ns AS (SELECT oid FROM pg_namespace WHERE nspname = $1)
341
+ SELECT NOT EXISTS (
342
+ SELECT 1 FROM pg_class c WHERE c.relnamespace = (SELECT oid FROM ns)
343
+ ) AND NOT EXISTS (
344
+ SELECT 1 FROM pg_proc p WHERE p.pronamespace = (SELECT oid FROM ns)
345
+ ) AND NOT EXISTS (
346
+ SELECT 1 FROM pg_type t WHERE t.typnamespace = (SELECT oid FROM ns)
347
+ ) AND NOT EXISTS (
348
+ SELECT 1 FROM pg_operator o WHERE o.oprnamespace = (SELECT oid FROM ns)
349
+ ) AS empty`;
350
+ var schemaIsEmpty = (pg, name) => Effect5.map(pg.unsafe(SCHEMA_EMPTY_SQL, [name]), (rows) => rows[0]?.empty ?? true);
351
+ var isDependentObjectsError = (error) => {
352
+ const cause = error.reason.cause;
353
+ return typeof cause === "object" && cause !== null && cause.code === "2BP01";
354
+ };
355
+ var isDuplicateSchemaError = (error) => {
356
+ const cause = error.reason.cause;
357
+ return typeof cause === "object" && cause !== null && cause.code === "42P06";
358
+ };
359
+ // src/postgres/schema.ts
360
+ import { Resource as Resource2 } from "alchemy";
361
+ import { Unowned as Unowned2 } from "alchemy/AdoptPolicy";
362
+ import * as Provider2 from "alchemy/Provider";
363
+ import * as Effect11 from "effect/Effect";
364
+
365
+ // src/postgres/schema-identity.ts
366
+ import * as Effect6 from "effect/Effect";
367
+
368
+ // src/postgres/schema-errors.ts
369
+ import * as Data2 from "effect/Data";
370
+
371
+ class PostgresSchemaNameRefused extends Data2.TaggedError("PostgresSchemaNameRefused") {
372
+ get message() {
373
+ return `Postgres.Schema "${this.name}": ${String(this.byteLength)} UTF-8 bytes, over the ` + `${String(this.limit)}-byte limit (NAMEDATALEN 64, minus the terminator). PostgreSQL would ` + "accept the CREATE and silently truncate the name with only a NOTICE, so this plan refuses " + "it instead of creating a schema no later read will ever find again.";
374
+ }
375
+ }
376
+
377
+ class PostgresSchemaRenameRefused extends Data2.TaggedError("PostgresSchemaRenameRefused") {
378
+ get message() {
379
+ return `Postgres.Schema: the declared name changed from "${this.from}" to "${this.to}". A ` + "rename is refused during plan or apply — this provider never runs ALTER SCHEMA … RENAME. Declare a new " + "logical id for the new name and remove the old one once its contents have moved.";
380
+ }
381
+ }
382
+
383
+ class PostgresSchemaOwnerMissing extends Data2.TaggedError("PostgresSchemaOwnerMissing") {
384
+ get message() {
385
+ return `Postgres.Schema "${this.schema}": AUTHORIZATION "${this.owner}" is not a role in pg_roles.`;
386
+ }
387
+ }
388
+
389
+ class PostgresSchemaDrift extends Data2.TaggedError("PostgresSchemaDrift") {
390
+ get message() {
391
+ return `Postgres.Schema "${this.schema}": ${this.prop} is declared as ` + `${JSON.stringify(this.declared)} but the live schema has ${JSON.stringify(this.live)}. ` + "This provider never issues ALTER SCHEMA — update the declaration to match the live value, " + "or change it by hand and re-plan.";
392
+ }
393
+ }
394
+
395
+ class PostgresSchemaCreateVanished extends Data2.TaggedError("PostgresSchemaCreateVanished") {
396
+ get message() {
397
+ return `Postgres.Schema "${this.schema}": CREATE SCHEMA raised no error, but the schema is still ` + "absent on the immediate re-read. Re-plan — this is not a state this provider expects to reach.";
398
+ }
399
+ }
400
+
401
+ class PostgresSchemaDropNotEmptyError extends Data2.TaggedError("PostgresSchemaDropNotEmptyError") {
402
+ get message() {
403
+ return `Postgres.Schema "${this.schema}": DROP SCHEMA refused because the schema is not empty and ` + "cascade is false. Declare cascade: true to drop its contents, or empty it by hand first.";
404
+ }
405
+ }
406
+
407
+ class PostgresSchemaWrongDatabase extends Data2.TaggedError("PostgresSchemaWrongDatabase") {
408
+ get message() {
409
+ return `Postgres.Schema "${this.schema}": declared in database "${this.declared}" but the ` + `connection answers current_database() "${this.connected}". The handlers open the declared ` + "database themselves, so this points at broken provider wiring — fix it before touching any " + "schema, or the next create would land in the wrong database.";
410
+ }
411
+ }
412
+
413
+ class PostgresSchemaDeleteForeignRefused extends Data2.TaggedError("PostgresSchemaDeleteForeignRefused") {
414
+ get message() {
415
+ const proof = this.lastOid === undefined || this.lastOwner === undefined ? "delete carried no persisted oid/owner to prove ownership" : `the persisted proof is oid ${String(this.lastOid)}, owner "${this.lastOwner}"`;
416
+ return `Postgres.Schema "${this.schema}": the live schema in database "${this.database}" (oid ` + `${String(this.liveOid)}, owner "${this.liveOwner}") is not the one this resource created — ` + `${proof}. No DROP was issued: a schema with this name was created out of band. Drop it by ` + "hand, or adopt it on a resource whose declared props match it.";
417
+ }
418
+ }
419
+
420
+ class PostgresSchemaDatabaseRefused extends Data2.TaggedError("PostgresSchemaDatabaseRefused") {
421
+ get message() {
422
+ return `Postgres.Schema: the declared database changed from "${this.from}" to "${this.to}". A ` + "schema is never moved across databases — declare a new logical id in the new database and " + "remove the old one once its contents have moved.";
423
+ }
424
+ }
425
+
426
+ class PostgresSchemaExistsRefused extends Data2.TaggedError("PostgresSchemaExistsRefused") {
427
+ get message() {
428
+ return `Postgres.Schema "${this.schema}" in "${this.database}" already exists. Re-plan with ` + "--adopt (adopt(true)) to adopt it explicitly; crash recovery also requires adoption. " + "No existing schema was changed.";
429
+ }
430
+ }
431
+
432
+ class PostgresSchemaIdentityRefused extends Data2.TaggedError("PostgresSchemaIdentityRefused") {
433
+ get message() {
434
+ return `Postgres.Schema "${this.schema}": live oid ${String(this.liveOid)} differs from stored oid ` + `${String(this.storedOid)}. The schema was dropped and recreated outside this stack. ` + "Remove stale state and explicitly adopt the replacement before managing it.";
435
+ }
436
+ }
437
+
438
+ // src/postgres/schema-identity.ts
439
+ var assertSchemaTarget = (props, output) => Effect6.gen(function* () {
440
+ if (output === undefined)
441
+ return;
442
+ if (props.name !== output.name)
443
+ return yield* Effect6.fail(new PostgresSchemaRenameRefused({ from: output.name, to: props.name }));
444
+ if (props.database !== output.database)
445
+ return yield* Effect6.fail(new PostgresSchemaDatabaseRefused({ from: output.database, to: props.database }));
446
+ });
447
+ var assertSchemaIdentity = (live, output) => live.oid === output.oid ? Effect6.void : Effect6.fail(new PostgresSchemaIdentityRefused({
448
+ schema: live.name,
449
+ storedOid: output.oid,
450
+ liveOid: live.oid
451
+ }));
452
+
453
+ // src/postgres/schema-diff.ts
454
+ import { isResolved as isResolved2 } from "alchemy/Diff";
455
+ import * as Effect7 from "effect/Effect";
456
+ var diffPostgresSchema = (news, output, olds) => Effect7.gen(function* () {
457
+ if (output === undefined || !isResolved2(news))
458
+ return;
459
+ yield* assertSchemaTarget(news, output);
460
+ const nameRefusal = schemaNameByteRefusal(news.name);
461
+ if (nameRefusal !== undefined) {
462
+ return yield* Effect7.fail(new PostgresSchemaNameRefused({ name: news.name, ...nameRefusal }));
463
+ }
464
+ const comment = normalizedComment(news.comment);
465
+ const changed = news.owner !== undefined && news.owner !== output.owner || comment !== undefined && comment !== (output.comment ?? undefined) || (news.cascade ?? false) !== (olds?.cascade ?? false);
466
+ return changed ? { action: "update" } : { action: "noop" };
467
+ });
468
+
469
+ // src/postgres/schema-reconcile.ts
470
+ import * as Effect9 from "effect/Effect";
471
+
472
+ // src/postgres/schema-assert.ts
473
+ import * as Effect8 from "effect/Effect";
474
+ var assertDatabase = (props, pg) => Effect8.flatMap(currentDatabase(pg), (connected) => connected === props.database ? Effect8.void : Effect8.fail(new PostgresSchemaWrongDatabase({
475
+ schema: props.name,
476
+ declared: props.database,
477
+ connected
478
+ })));
479
+ var declaredOwner = (props, executingRole) => props.owner ?? executingRole;
480
+ var ownerDrift = (props, live, executingRole) => {
481
+ const declared = declaredOwner(props, executingRole);
482
+ return declared === live.owner ? undefined : { prop: "owner", declared, live: live.owner };
483
+ };
484
+ var liveDrift2 = (props, live, executingRole) => {
485
+ const owner = ownerDrift(props, live, executingRole);
486
+ if (owner !== undefined)
487
+ return owner;
488
+ const comment = normalizedComment(props.comment);
489
+ if (comment !== undefined && comment !== (live.comment ?? undefined)) {
490
+ return { prop: "comment", declared: comment, live: live.comment };
491
+ }
492
+ return;
493
+ };
494
+ var refuse = (props, drift) => Effect8.fail(new PostgresSchemaDrift({
495
+ schema: props.name,
496
+ prop: drift.prop,
497
+ declared: drift.declared,
498
+ live: drift.live
499
+ }));
500
+ var assertOwner = (props, row, executingRole) => {
501
+ const drift = ownerDrift(props, row, executingRole);
502
+ return drift === undefined ? Effect8.void : refuse(props, drift);
503
+ };
504
+ var assertLive = (props, row, executingRole) => {
505
+ const drift = liveDrift2(props, row, executingRole);
506
+ return drift === undefined ? Effect8.succeed(row) : refuse(props, drift);
507
+ };
508
+ var deleteForeignRefusal = (props, live, output) => {
509
+ if (output !== undefined && output.oid === live.oid && output.owner === live.owner) {
510
+ return;
511
+ }
512
+ return new PostgresSchemaDeleteForeignRefused({
513
+ schema: props.name,
514
+ database: props.database,
515
+ liveOid: live.oid,
516
+ liveOwner: live.owner,
517
+ lastOid: output?.oid,
518
+ lastOwner: output?.owner
519
+ });
520
+ };
521
+
522
+ // src/postgres/schema-reconcile.ts
523
+ var reconcileWithClient2 = (pg, props, output) => Effect9.gen(function* () {
524
+ yield* assertSchemaTarget(props, output);
525
+ yield* assertDatabase(props, pg);
526
+ const executingRole = props.owner !== undefined ? props.owner : yield* currentUser(pg);
527
+ const observed = yield* selectSchema(pg, props.name);
528
+ if (observed === undefined) {
529
+ if (props.owner !== undefined) {
530
+ const ownerPresent = yield* roleExists(pg, props.owner);
531
+ if (!ownerPresent) {
532
+ return yield* Effect9.fail(new PostgresSchemaOwnerMissing({ schema: props.name, owner: props.owner }));
533
+ }
534
+ }
535
+ const createRefusal = (error) => isDuplicateSchemaError(error) ? new PostgresSchemaExistsRefused({ schema: props.name, database: props.database }) : error;
536
+ yield* pg.unsafe(buildCreateSchemaSql(props)).pipe(Effect9.asVoid, Effect9.catchTag("SqlError", (error) => Effect9.fail(createRefusal(error))));
537
+ const created = yield* selectSchema(pg, props.name);
538
+ if (created === undefined) {
539
+ return yield* Effect9.fail(new PostgresSchemaCreateVanished({ schema: props.name }));
540
+ }
541
+ yield* assertOwner(props, created, executingRole);
542
+ const comment = normalizedComment(props.comment);
543
+ if (comment !== undefined && comment !== (created.comment ?? undefined)) {
544
+ yield* pg.unsafe(buildCommentSchemaSql(props.name, comment)).pipe(Effect9.asVoid);
545
+ const commented = yield* selectSchema(pg, props.name);
546
+ if (commented === undefined) {
547
+ return yield* Effect9.fail(new PostgresSchemaCreateVanished({ schema: props.name }));
548
+ }
549
+ return yield* assertLive(props, commented, executingRole);
550
+ }
551
+ return yield* assertLive(props, created, executingRole);
552
+ }
553
+ if (output === undefined) {
554
+ return yield* Effect9.fail(new PostgresSchemaExistsRefused({ schema: props.name, database: props.database }));
555
+ }
556
+ yield* assertSchemaIdentity(observed, output);
557
+ return yield* assertLive(props, observed, executingRole);
558
+ });
559
+
560
+ // src/postgres/schema-delete.ts
561
+ import * as Effect10 from "effect/Effect";
562
+ var dropGuarded = (pg, props) => Effect10.gen(function* () {
563
+ if (props.cascade !== true) {
564
+ const empty = yield* schemaIsEmpty(pg, props.name);
565
+ if (!empty) {
566
+ return yield* Effect10.fail(new PostgresSchemaDropNotEmptyError({ schema: props.name }));
567
+ }
568
+ }
569
+ const dropRefusal = (error) => props.cascade !== true && isDependentObjectsError(error) ? new PostgresSchemaDropNotEmptyError({ schema: props.name }) : error;
570
+ yield* pg.unsafe(buildDropSchemaSql(props.name, props.cascade === true)).pipe(Effect10.asVoid, Effect10.catchTag("SqlError", (error) => Effect10.fail(dropRefusal(error))));
571
+ });
572
+ var deleteWithClient = (pg, props, output) => Effect10.gen(function* () {
573
+ yield* assertDatabase(props, pg);
574
+ const live = yield* selectSchema(pg, props.name);
575
+ if (live === undefined)
576
+ return;
577
+ const refusal = deleteForeignRefusal(props, live, output);
578
+ if (refusal !== undefined)
579
+ return yield* Effect10.fail(refusal);
580
+ yield* dropGuarded(pg, props);
581
+ });
582
+
583
+ // src/postgres/schema.ts
584
+ var PostgresSchema = Resource2("Postgres.Schema", {
585
+ defaultRemovalPolicy: "retain"
586
+ });
587
+ var isPostgresSchema = (value) => (typeof value === "object" || typeof value === "function") && value !== null && value.Type === "Postgres.Schema";
588
+ var readWithClient2 = (pg, name) => selectSchema(pg, name);
589
+ var postgresSchemaHandlers = PostgresSchema.Provider.of({
590
+ list: () => Effect11.succeed([]),
591
+ nuke: { skip: true },
592
+ read: ({ olds, output }) => Effect11.gen(function* () {
593
+ const name = output?.name ?? olds.name;
594
+ const database = output?.database ?? olds.database;
595
+ const present = yield* withPg((pg) => databaseExists(pg, database));
596
+ if (!present)
597
+ return;
598
+ const live = yield* withPg((pg) => readWithClient2(pg, name), database);
599
+ if (live !== undefined && live.database !== database) {
600
+ return yield* Effect11.fail(new PostgresSchemaWrongDatabase({
601
+ schema: name,
602
+ declared: database,
603
+ connected: live.database
604
+ }));
605
+ }
606
+ if (live !== undefined && output !== undefined)
607
+ yield* assertSchemaIdentity(live, output);
608
+ return live === undefined ? undefined : Unowned2(live);
609
+ }),
610
+ diff: ({ news, output, olds }) => diffPostgresSchema(news, output, olds),
611
+ reconcile: ({ news, output }) => Effect11.gen(function* () {
612
+ const nameRefusal = schemaNameByteRefusal(news.name);
613
+ if (nameRefusal !== undefined) {
614
+ return yield* Effect11.fail(new PostgresSchemaNameRefused({ name: news.name, ...nameRefusal }));
615
+ }
616
+ yield* assertSchemaTarget(news, output);
617
+ return yield* withPg((pg) => reconcileWithClient2(pg, news, output), news.database);
618
+ }),
619
+ delete: ({ olds, output }) => Effect11.gen(function* () {
620
+ const present = yield* withPg((pg) => databaseExists(pg, olds.database));
621
+ if (!present)
622
+ return;
623
+ return yield* withPg((pg) => deleteWithClient(pg, olds, output), olds.database);
624
+ })
625
+ });
626
+ var PostgresSchemaProvider = () => Provider2.succeed(PostgresSchema, postgresSchemaHandlers);
627
+ // src/postgres/grants-attrs.ts
628
+ var SCHEMA_PRIVILEGES = ["usage", "create"];
629
+ var TABLE_PRIVILEGES = [
630
+ "select",
631
+ "insert",
632
+ "update",
633
+ "delete",
634
+ "truncate",
635
+ "references",
636
+ "trigger",
637
+ "maintain"
638
+ ];
639
+ var COLUMN_PRIVILEGES = ["select", "insert", "update", "references"];
640
+ var ALL_GRANT_WORDS = [...SCHEMA_PRIVILEGES, ...TABLE_PRIVILEGES];
641
+ var LETTER_FOR_PRIVILEGE = {
642
+ insert: "a",
643
+ select: "r",
644
+ update: "w",
645
+ delete: "d",
646
+ truncate: "D",
647
+ references: "x",
648
+ trigger: "t",
649
+ maintain: "m",
650
+ usage: "U",
651
+ create: "C"
652
+ };
653
+ var letterForPrivilege = (word) => {
654
+ const letter = LETTER_FOR_PRIVILEGE[word];
655
+ if (letter === undefined)
656
+ throw new Error(`grants-attrs: no ACL letter for "${word}"`);
657
+ return letter;
658
+ };
659
+ var grantsNameByteRefusal = (name) => {
660
+ const byteLength = utf8ByteLength(name);
661
+ return byteLength > POSTGRES_NAME_MAX_BYTES ? { byteLength, limit: POSTGRES_NAME_MAX_BYTES } : undefined;
662
+ };
663
+ // src/postgres/grants-sql.ts
664
+ var splitParts = (words) => {
665
+ const parts = [];
666
+ let optionCount = 0;
667
+ for (const word of words) {
668
+ const base = word.endsWith("*") ? word.slice(0, -1) : word;
669
+ if (!ALL_GRANT_WORDS.includes(base)) {
670
+ throw new Error(`grants-sql: "${base}" is not a privilege this family can grant — see the three ` + "vocabularies in grants-attrs.ts (traced to acl.h at REL_18_6)");
671
+ }
672
+ if (word.endsWith("*"))
673
+ optionCount += 1;
674
+ parts.push(base);
675
+ }
676
+ if (optionCount > 0 && optionCount < words.length) {
677
+ throw new Error(`grants-sql: a mixed grant-option list cannot be one GRANT statement: ${words.join(", ")}` + " — split it with splitGrantWords first (one option clause grants the option to every word)");
678
+ }
679
+ return { parts, anyOption: optionCount > 0 };
680
+ };
681
+ var optionClause = (anyOption) => anyOption ? " WITH GRANT OPTION" : "";
682
+ var grantTableSql = (schema, table, role, words) => {
683
+ const { parts, anyOption } = splitParts(words);
684
+ return `GRANT ${parts.join(", ")} ON ${quoteIdent(schema)}.${quoteIdent(table)} TO ${quoteIdent(role)}${optionClause(anyOption)}`;
685
+ };
686
+ var grantColumnSql = (schema, table, column, role, words) => {
687
+ const { parts, anyOption } = splitParts(words);
688
+ const columnList = `(${quoteIdent(column)})`;
689
+ return `GRANT ${parts.map((word) => `${word} ${columnList}`).join(", ")} ON ${quoteIdent(schema)}.${quoteIdent(table)} TO ${quoteIdent(role)}${optionClause(anyOption)}`;
690
+ };
691
+ var grantSchemaSql = (schema, role, words) => {
692
+ const { parts, anyOption } = splitParts(words);
693
+ return `GRANT ${parts.join(", ")} ON SCHEMA ${quoteIdent(schema)} TO ${quoteIdent(role)}${optionClause(anyOption)}`;
694
+ };
695
+ var grantDefaultSql = (schema, forRole, role, words) => {
696
+ const { parts, anyOption } = splitParts(words);
697
+ return `ALTER DEFAULT PRIVILEGES FOR ROLE ${quoteIdent(forRole)} IN SCHEMA ${quoteIdent(schema)} GRANT ${parts.join(", ")} ON TABLES TO ${quoteIdent(role)}${optionClause(anyOption)}`;
698
+ };
699
+ var revokeTableSql = (schema, table, role) => `REVOKE ALL ON ${quoteIdent(schema)}.${quoteIdent(table)} FROM ${quoteIdent(role)}`;
700
+ var revokeColumnSql = (schema, table, column, role) => `REVOKE ALL (${quoteIdent(column)}) ON ${quoteIdent(schema)}.${quoteIdent(table)} FROM ${quoteIdent(role)}`;
701
+ var revokeSchemaSql = (schema, role) => `REVOKE ALL ON SCHEMA ${quoteIdent(schema)} FROM ${quoteIdent(role)}`;
702
+ var revokeDefaultSql = (schema, forRole, role) => `ALTER DEFAULT PRIVILEGES FOR ROLE ${quoteIdent(forRole)} IN SCHEMA ${quoteIdent(schema)} REVOKE ALL ON TABLES FROM ${quoteIdent(role)}`;
703
+ var revokePublicSchemaSql = (schema) => `REVOKE ALL ON SCHEMA ${quoteIdent(schema)} FROM PUBLIC`;
704
+ var revokePublicTablesSql = (schema) => `REVOKE ALL ON ALL TABLES IN SCHEMA ${quoteIdent(schema)} FROM PUBLIC`;
705
+ // src/postgres/grants.ts
706
+ import { Resource as Resource3 } from "alchemy";
707
+ import { Unowned as Unowned3 } from "alchemy/AdoptPolicy";
708
+ import * as Provider3 from "alchemy/Provider";
709
+ import * as Effect17 from "effect/Effect";
710
+
711
+ // src/postgres/grants-declare.ts
712
+ var cleanWords = (words) => [...new Set(words)].sort();
713
+ var baseWord = (word) => word.endsWith("*") ? word.slice(0, -1) : word;
714
+ var resolveProps = (props) => ({
715
+ role: props.role,
716
+ database: props.database,
717
+ schema: props.schema,
718
+ schemaPrivileges: cleanWords([
719
+ ...props.schemaUsage === false ? [] : ["usage"],
720
+ ...props.schemaCreate === true ? ["create"] : []
721
+ ]),
722
+ tables: [...props.tables ?? []].map((table) => ({ table: table.table, privileges: cleanWords(table.privileges) })).sort((a, b) => a.table.localeCompare(b.table)),
723
+ columns: [...props.columnGrants ?? []].map((column) => ({
724
+ table: column.table,
725
+ column: column.column,
726
+ privileges: cleanWords(column.privileges)
727
+ })).sort((a, b) => a.table.localeCompare(b.table) || a.column.localeCompare(b.column)),
728
+ defaults: [...props.defaultPrivileges ?? []].map((entry) => ({ forRole: entry.forRole, privileges: cleanWords(entry.privileges) })).sort((a, b) => a.forRole.localeCompare(b.forRole)),
729
+ publicSchemaRevoked: props.revokeFromPublic === true,
730
+ publicTablesRevoked: props.revokeFromPublic === true
731
+ });
732
+ var splitGrantWords = (words) => ({
733
+ plain: words.filter((word) => !word.endsWith("*")),
734
+ grantable: words.filter((word) => word.endsWith("*"))
735
+ });
736
+ var declaredNames = (declared) => ({
737
+ role: declared.role,
738
+ database: declared.database,
739
+ schema: declared.schema,
740
+ tables: declared.tables.map((table) => table.table),
741
+ columns: declared.columns.map(({ table, column }) => ({ table, column })),
742
+ defaults: declared.defaults.map((entry) => entry.forRole)
743
+ });
744
+ var namesFromAttrs = (attrs) => ({
745
+ role: attrs.role,
746
+ database: attrs.database,
747
+ schema: attrs.schema,
748
+ tables: attrs.tables.map((table) => table.table),
749
+ columns: attrs.columns.map(({ table, column }) => ({ table, column })),
750
+ defaults: attrs.defaults.map((entry) => entry.forRole)
751
+ });
752
+ var clearedDeclaration = (declared) => ({
753
+ ...declared,
754
+ schemaPrivileges: [],
755
+ tables: declared.tables.map((table) => ({ table: table.table, privileges: [] })),
756
+ columns: declared.columns.map((column) => ({
757
+ table: column.table,
758
+ column: column.column,
759
+ privileges: []
760
+ })),
761
+ defaults: declared.defaults.map((entry) => ({ forRole: entry.forRole, privileges: [] })),
762
+ publicSchemaRevoked: false,
763
+ publicTablesRevoked: false
764
+ });
765
+ var removedNames = (output, declared) => {
766
+ if (output === undefined)
767
+ return;
768
+ const keepTables = new Set(declared.tables.map((table) => table.table));
769
+ const keepColumns = new Set(declared.columns.map((column) => `${column.table}\x00${column.column}`));
770
+ const keepDefaults = new Set(declared.defaults.map((entry) => entry.forRole));
771
+ const tables = output.tables.filter((table) => !keepTables.has(table.table)).map((table) => ({ table: table.table, privileges: [] }));
772
+ const columns = output.columns.filter((column) => !keepColumns.has(`${column.table}\x00${column.column}`)).map((column) => ({
773
+ table: column.table,
774
+ column: column.column,
775
+ privileges: []
776
+ }));
777
+ const defaults = output.defaults.filter((entry) => !keepDefaults.has(entry.forRole)).map((entry) => ({ forRole: entry.forRole, privileges: [] }));
778
+ if (tables.length === 0 && columns.length === 0 && defaults.length === 0)
779
+ return;
780
+ return { schema: declared.schema, role: declared.role, tables, columns, defaults };
781
+ };
782
+
783
+ // src/postgres/grants-ops.ts
784
+ import * as Effect16 from "effect/Effect";
785
+ import { isResolved as isResolved3 } from "alchemy/Diff";
786
+
787
+ // src/postgres/grants-refuse.ts
788
+ var wordRefusal = (words, vocabulary) => {
789
+ for (const word of words) {
790
+ if (word === "*")
791
+ return word;
792
+ const bare = baseWord(word);
793
+ if (!vocabulary.includes(bare))
794
+ return bare;
795
+ }
796
+ return;
797
+ };
798
+ var listRefusal = (words, vocabulary, prop) => {
799
+ const word = wordRefusal(words, vocabulary);
800
+ if (word !== undefined)
801
+ return { kind: "privilege", prop, word };
802
+ const seen = new Set;
803
+ for (const entry of words) {
804
+ const base = baseWord(entry);
805
+ if (seen.has(base))
806
+ return { kind: "duplicate", prop, name: base };
807
+ seen.add(base);
808
+ }
809
+ return;
810
+ };
811
+ var declarationRefusal = (declared) => {
812
+ const schema = listRefusal(declared.schemaPrivileges, SCHEMA_PRIVILEGES, "schemaPrivileges");
813
+ if (schema !== undefined)
814
+ return schema;
815
+ for (const table2 of declared.tables) {
816
+ const refusal = listRefusal(table2.privileges, TABLE_PRIVILEGES, `tables.${table2.table}`);
817
+ if (refusal !== undefined)
818
+ return refusal;
819
+ }
820
+ for (const column2 of declared.columns) {
821
+ const refusal = listRefusal(column2.privileges, COLUMN_PRIVILEGES, `columnGrants.${column2.table}.${column2.column}`);
822
+ if (refusal !== undefined)
823
+ return refusal;
824
+ }
825
+ for (const entry of declared.defaults) {
826
+ const refusal = listRefusal(entry.privileges, TABLE_PRIVILEGES, `defaultPrivileges.${entry.forRole}`);
827
+ if (refusal !== undefined)
828
+ return refusal;
829
+ }
830
+ const duplicate = (names) => {
831
+ const seen = new Set;
832
+ for (const name of names) {
833
+ if (seen.has(name))
834
+ return name;
835
+ seen.add(name);
836
+ }
837
+ return;
838
+ };
839
+ const table = duplicate(declared.tables.map((entry) => entry.table));
840
+ if (table !== undefined)
841
+ return { kind: "duplicate", prop: "tables", name: table };
842
+ const column = duplicate(declared.columns.map((entry) => `${entry.table}\x00${entry.column}`));
843
+ if (column !== undefined)
844
+ return { kind: "duplicate", prop: "columnGrants", name: column.replace("\x00", ".") };
845
+ const forRole = duplicate(declared.defaults.map((entry) => entry.forRole));
846
+ if (forRole !== undefined)
847
+ return { kind: "duplicate", prop: "defaultPrivileges", name: forRole };
848
+ return;
849
+ };
850
+ var grantsNamesRefusal = (declared) => {
851
+ const names = [
852
+ declared.role,
853
+ declared.database,
854
+ declared.schema,
855
+ ...declared.tables.map((table) => table.table),
856
+ ...declared.columns.flatMap((column) => [column.table, column.column]),
857
+ ...declared.defaults.map((entry) => entry.forRole)
858
+ ];
859
+ for (const name of names) {
860
+ const refusal = grantsNameByteRefusal(name);
861
+ if (refusal !== undefined)
862
+ return { name, ...refusal };
863
+ }
864
+ return;
865
+ };
866
+
867
+ // src/postgres/grants-plan-restore.ts
868
+ var tableWords = (live, table) => live.tables.find((entry) => entry.table === table)?.role ?? [];
869
+ var columnWords = (live, table, column) => live.columns.find((entry) => entry.table === table && entry.column === column)?.role ?? [];
870
+ var removalRevokes = (removed, live) => {
871
+ const ownedTables = new Set(live.ownedTables);
872
+ return {
873
+ tables: removed.tables.filter((table) => !ownedTables.has(table.table) && tableWords(live, table.table).length > 0).map((table) => table.table),
874
+ columns: removed.columns.filter((column) => !ownedTables.has(column.table) && columnWords(live, column.table, column.column).length > 0).map(({ table, column }) => ({ table, column }))
875
+ };
876
+ };
877
+
878
+ // src/postgres/grants-words.ts
879
+ var encodeWord = (row) => `${row.privilege.toLowerCase()}${row.grantable ? "*" : ""}`;
880
+ var wordsOf = (rows, wantPublic) => [...new Set(rows.filter((row) => row.public === wantPublic).map(encodeWord))].sort();
881
+ var PUBLIC_REVOCABLE_RELKINDS = ["r", "v", "m", "f", "p"];
882
+ var relkindIsPublicRevocable = (relkind) => PUBLIC_REVOCABLE_RELKINDS.includes(relkind);
883
+
884
+ // src/postgres/grants-plan.ts
885
+ var wordsDiffer = (declared, live) => declared.length !== live.length || declared.some((word, index) => word !== live[index]);
886
+ var grantStatements = (words, grant) => {
887
+ const { plain, grantable } = splitGrantWords(words);
888
+ return [
889
+ ...plain.length > 0 ? [grant(plain)] : [],
890
+ ...grantable.length > 0 ? [grant(grantable)] : []
891
+ ];
892
+ };
893
+ var liveTableWords = (live, table) => live.tables.find((entry) => entry.table === table)?.role ?? [];
894
+ var liveColumnWords = (live, table, column) => live.columns.find((entry) => entry.table === table && entry.column === column)?.role ?? [];
895
+ var liveDefaultWords = (live, forRole) => live.defaults.find((entry) => entry.forRole === forRole)?.role ?? [];
896
+ var columnKey = (table, column) => `${table}\x00${column}`;
897
+ var restoredColumnGrants = (declared, live, table, skip) => {
898
+ const statements = [];
899
+ for (const column of live.columns) {
900
+ if (column.table !== table || skip.has(columnKey(column.table, column.column)))
901
+ continue;
902
+ if (column.restorable.length === 0)
903
+ continue;
904
+ statements.push(...grantStatements(column.restorable, (words) => grantColumnSql(declared.schema, column.table, column.column, declared.role, words)));
905
+ }
906
+ return statements;
907
+ };
908
+ var planRepair = (declared, live, prior = {}) => {
909
+ const statements = [];
910
+ const ownedTables = new Set(live.ownedTables);
911
+ if (!live.schemaOwnedByRole && wordsDiffer(declared.schemaPrivileges, live.schema.role)) {
912
+ statements.push(revokeSchemaSql(declared.schema, declared.role));
913
+ statements.push(...grantStatements(declared.schemaPrivileges, (words) => grantSchemaSql(declared.schema, declared.role, words)));
914
+ }
915
+ const revokedTables = new Set(prior.revokedTables ?? []);
916
+ const skipColumns = new Set([
917
+ ...declared.columns.map((column) => columnKey(column.table, column.column)),
918
+ ...(prior.revokedColumns ?? []).map((column) => columnKey(column.table, column.column))
919
+ ]);
920
+ const declaredTableNames = new Set(declared.tables.map((table) => table.table));
921
+ for (const table of declared.tables) {
922
+ if (ownedTables.has(table.table))
923
+ continue;
924
+ if (wordsDiffer(table.privileges, liveTableWords(live, table.table))) {
925
+ revokedTables.add(table.table);
926
+ statements.push(revokeTableSql(declared.schema, table.table, declared.role));
927
+ statements.push(...grantStatements(table.privileges, (words) => grantTableSql(declared.schema, table.table, declared.role, words)));
928
+ statements.push(...restoredColumnGrants(declared, live, table.table, skipColumns));
929
+ }
930
+ }
931
+ for (const table of prior.revokedTables ?? []) {
932
+ if (declaredTableNames.has(table) || ownedTables.has(table))
933
+ continue;
934
+ statements.push(...restoredColumnGrants(declared, live, table, skipColumns));
935
+ }
936
+ for (const column of declared.columns) {
937
+ if (ownedTables.has(column.table))
938
+ continue;
939
+ const liveWords = revokedTables.has(column.table) ? [] : liveColumnWords(live, column.table, column.column);
940
+ if (wordsDiffer(column.privileges, liveWords)) {
941
+ statements.push(revokeColumnSql(declared.schema, column.table, column.column, declared.role));
942
+ statements.push(...grantStatements(column.privileges, (words) => grantColumnSql(declared.schema, column.table, column.column, declared.role, words)));
943
+ }
944
+ }
945
+ for (const entry of declared.defaults) {
946
+ if (wordsDiffer(entry.privileges, liveDefaultWords(live, entry.forRole))) {
947
+ statements.push(revokeDefaultSql(declared.schema, entry.forRole, declared.role));
948
+ statements.push(...grantStatements(entry.privileges, (words) => grantDefaultSql(declared.schema, entry.forRole, declared.role, words)));
949
+ }
950
+ }
951
+ if (declared.publicSchemaRevoked && live.schema.public.length > 0) {
952
+ statements.push(revokePublicSchemaSql(declared.schema));
953
+ }
954
+ if (declared.publicTablesRevoked && (live.tables.some((table) => relkindIsPublicRevocable(table.relkind) && table.public.length > 0) || live.columns.some((column) => column.public.length > 0))) {
955
+ statements.push(revokePublicTablesSql(declared.schema));
956
+ }
957
+ return statements;
958
+ };
959
+ var planRevocations = (removed, live) => {
960
+ const statements = [];
961
+ const revoked = removalRevokes(removed, live);
962
+ for (const table of revoked.tables) {
963
+ statements.push(revokeTableSql(removed.schema, table, removed.role));
964
+ }
965
+ for (const column of revoked.columns) {
966
+ statements.push(revokeColumnSql(removed.schema, column.table, column.column, removed.role));
967
+ }
968
+ for (const entry of removed.defaults) {
969
+ if (liveDefaultWords(live, entry.forRole).length > 0) {
970
+ statements.push(revokeDefaultSql(removed.schema, entry.forRole, removed.role));
971
+ }
972
+ }
973
+ return statements;
974
+ };
975
+ var planReconcile = (declared, live, removed) => {
976
+ const revoked = removed === undefined ? undefined : removalRevokes(removed, live);
977
+ return [
978
+ ...removed !== undefined ? planRevocations(removed, live) : [],
979
+ ...planRepair(declared, live, {
980
+ revokedTables: revoked?.tables ?? [],
981
+ revokedColumns: removed?.columns ?? []
982
+ })
983
+ ];
984
+ };
985
+
986
+ // src/postgres/grants-diff.ts
987
+ var attributesOf = (live, names) => ({
988
+ role: names.role,
989
+ database: names.database,
990
+ schema: names.schema,
991
+ schemaPrivileges: live.schema.role,
992
+ tables: names.tables.map((table) => ({
993
+ table,
994
+ privileges: liveTableWords(live, table)
995
+ })),
996
+ columns: names.columns.map(({ table, column }) => ({
997
+ table,
998
+ column,
999
+ privileges: liveColumnWords(live, table, column)
1000
+ })),
1001
+ defaults: names.defaults.map((forRole) => ({
1002
+ forRole,
1003
+ privileges: liveDefaultWords(live, forRole)
1004
+ })),
1005
+ publicSchemaRevoked: live.schema.public.length === 0,
1006
+ publicTablesRevoked: live.tables.every((table) => !relkindIsPublicRevocable(table.relkind) || table.public.length === 0) && live.columns.every((column) => column.public.length === 0),
1007
+ schemaOwnedByRole: live.schemaOwnedByRole,
1008
+ ownedTables: names.tables.filter((table) => live.ownedTables.includes(table))
1009
+ });
1010
+ var grantsDiffer = (declared, live) => {
1011
+ if (declared.role !== live.role || declared.database !== live.database || declared.schema !== live.schema) {
1012
+ return true;
1013
+ }
1014
+ if (declared.publicSchemaRevoked && !live.publicSchemaRevoked)
1015
+ return true;
1016
+ if (declared.publicTablesRevoked && !live.publicTablesRevoked)
1017
+ return true;
1018
+ if (!live.schemaOwnedByRole && wordsDiffer(declared.schemaPrivileges, live.schemaPrivileges)) {
1019
+ return true;
1020
+ }
1021
+ if (declared.tables.length !== live.tables.length)
1022
+ return true;
1023
+ const ownedTables = new Set(live.ownedTables);
1024
+ if (declared.tables.some((table, index) => table.table !== (live.tables[index]?.table ?? null) || !ownedTables.has(table.table) && wordsDiffer(table.privileges, live.tables[index]?.privileges ?? []))) {
1025
+ return true;
1026
+ }
1027
+ if (declared.columns.length !== live.columns.length)
1028
+ return true;
1029
+ if (declared.columns.some((column, index) => column.table !== (live.columns[index]?.table ?? null) || column.column !== (live.columns[index]?.column ?? null) || !ownedTables.has(column.table) && wordsDiffer(column.privileges, live.columns[index]?.privileges ?? []))) {
1030
+ return true;
1031
+ }
1032
+ if (declared.defaults.length !== live.defaults.length)
1033
+ return true;
1034
+ if (declared.defaults.some((entry, index) => entry.forRole !== (live.defaults[index]?.forRole ?? null) || wordsDiffer(entry.privileges, live.defaults[index]?.privileges ?? []))) {
1035
+ return true;
1036
+ }
1037
+ return false;
1038
+ };
1039
+
1040
+ // src/postgres/grants-existence.ts
1041
+ import * as Effect12 from "effect/Effect";
1042
+
1043
+ // src/postgres/grants-errors.ts
1044
+ import * as Data3 from "effect/Data";
1045
+
1046
+ class PostgresGrantsPrivilegeRefused extends Data3.TaggedError("PostgresGrantsPrivilegeRefused") {
1047
+ get message() {
1048
+ return `Postgres.Grants: "${this.prop}" declares "${this.word}", which is not in the vocabulary ` + "PostgreSQL 18 grants on that object class (see grants-attrs.ts for the three sets, traced " + "to acl.h at REL_18_6). Column grants take select/insert/update/references only — TRUNCATE, " + "TRIGGER and MAINTAIN are table-wide.";
1049
+ }
1050
+ }
1051
+
1052
+ class PostgresGrantsNameRefused extends Data3.TaggedError("PostgresGrantsNameRefused") {
1053
+ get message() {
1054
+ return `Postgres.Grants: the name "${this.name}" is ${String(this.byteLength)} UTF-8 bytes, over the ` + `${String(this.limit)}-byte limit (NAMEDATALEN 64, minus the terminator). PostgreSQL would ` + "accept the statement and silently truncate the name with only a NOTICE, so this plan refuses " + "it instead of granting on an object no later read will ever find again.";
1055
+ }
1056
+ }
1057
+
1058
+ class PostgresGrantsDuplicateObject extends Data3.TaggedError("PostgresGrantsDuplicateObject") {
1059
+ get message() {
1060
+ return `Postgres.Grants: "${this.prop}" declares "${this.name}" twice. Merge the entries into one — ` + "the declaration must state each object at most once, and each privilege word in exactly one " + "form: plain, or WITH GRANT OPTION (`select` and `select*` cannot both hold, the server keeps " + "one aclitem per grantee per grantor).";
1061
+ }
1062
+ }
1063
+
1064
+ class PostgresGrantsRetargetRefused extends Data3.TaggedError("PostgresGrantsRetargetRefused") {
1065
+ get message() {
1066
+ return `Postgres.Grants: the declared ${this.prop} changed from "${this.from}" to "${this.to}". A ` + "retarget is refused at plan — declare a new logical id for the new target and delete the old " + "one (its retain policy keeps the grants until a destroy revokes them).";
1067
+ }
1068
+ }
1069
+
1070
+ class PostgresGrantsDatabaseMismatch extends Data3.TaggedError("PostgresGrantsDatabaseMismatch") {
1071
+ get message() {
1072
+ return `Postgres.Grants: the declaration names database "${this.declared}" but ` + `current_database() is "${this.connected}". The handlers open the declared database and ` + "prove it before any GRANT or REVOKE — a mismatch means that open did not land there.";
1073
+ }
1074
+ }
1075
+
1076
+ class PostgresGrantsSchemaMissing extends Data3.TaggedError("PostgresGrantsSchemaMissing") {
1077
+ get message() {
1078
+ return `Postgres.Grants: schema "${this.schema}" is not in pg_namespace. Grants are written into a ` + "schema that already exists — declare a Postgres.Schema (or create it by hand) before the " + "grant set that scopes to it.";
1079
+ }
1080
+ }
1081
+
1082
+ class PostgresGrantsDatabaseMissing extends Data3.TaggedError("PostgresGrantsDatabaseMissing") {
1083
+ get message() {
1084
+ return `Postgres.Grants: database "${this.database}" is not in pg_database. Grants are written into ` + "a database that already exists — declare a Postgres.Database (or create it by hand) before " + "the grant set that scopes to it.";
1085
+ }
1086
+ }
1087
+
1088
+ class PostgresGrantsRoleMissing extends Data3.TaggedError("PostgresGrantsRoleMissing") {
1089
+ get message() {
1090
+ return `Postgres.Grants: role "${this.role}" is not in pg_roles. GRANT and ALTER DEFAULT PRIVILEGES ` + "would fail against it — create the role (a group role per seat, membership owned by a human) " + "before declaring its grants.";
1091
+ }
1092
+ }
1093
+
1094
+ class PostgresGrantsTableMissing extends Data3.TaggedError("PostgresGrantsTableMissing") {
1095
+ get message() {
1096
+ return `Postgres.Grants: table "${this.schema}"."${this.table}" does not exist. The declaration ` + "names a relation before it is created; create the table (or fix the name) before granting.";
1097
+ }
1098
+ }
1099
+
1100
+ class PostgresGrantsColumnMissing extends Data3.TaggedError("PostgresGrantsColumnMissing") {
1101
+ get message() {
1102
+ return `Postgres.Grants: column "${this.column}" of "${this.schema}"."${this.table}" does not ` + "exist. Declare the column (or fix its name) before granting on it.";
1103
+ }
1104
+ }
1105
+
1106
+ class PostgresGrantsRepairRefused extends Data3.TaggedError("PostgresGrantsRepairRefused") {
1107
+ get message() {
1108
+ return `Postgres.Grants: the repair on schema "${this.schema}" for role "${this.role}" re-read as ` + `still drifted; these statements would run again: ${this.remaining.join(" ; ")}. A grant made ` + "by another role survives this resource’s REVOKE (only its grantor or the object’s " + "owner can revoke it) — revoke it as that grantor or the owner, or declare the privilege so " + "the set matches.";
1109
+ }
1110
+ }
1111
+
1112
+ // src/postgres/grants-existence.ts
1113
+ var TABLES_PRESENT_SQL = `SELECT
1114
+ c.relname AS table
1115
+ FROM pg_class c
1116
+ WHERE c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)
1117
+ AND c.relname::text IN (SELECT json_array_elements_text($2::json))`;
1118
+ var findMissingTable = (pg, schema, tables) => tables.length === 0 ? Effect12.succeed(undefined) : Effect12.map(pg.unsafe(TABLES_PRESENT_SQL, [schema, JSON.stringify(tables)]), (rows) => {
1119
+ const present = new Set(rows.map((row) => row.table));
1120
+ return tables.find((table) => !present.has(table));
1121
+ });
1122
+ var COLUMNS_PRESENT_SQL = `SELECT
1123
+ c.relname AS table,
1124
+ a.attname AS column
1125
+ FROM pg_attribute a
1126
+ JOIN pg_class c ON c.oid = a.attrelid
1127
+ WHERE c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)
1128
+ AND a.attnum > 0
1129
+ AND NOT a.attisdropped
1130
+ AND (c.relname, a.attname) IN (
1131
+ SELECT x->>0, x->>1 FROM json_array_elements($2::json) x
1132
+ )`;
1133
+ var findMissingColumn = (pg, schema, columns) => columns.length === 0 ? Effect12.succeed(undefined) : Effect12.map(pg.unsafe(COLUMNS_PRESENT_SQL, [
1134
+ schema,
1135
+ JSON.stringify(columns.map(({ table, column }) => [table, column]))
1136
+ ]), (rows) => {
1137
+ const present = new Set(rows.map((row) => `${row.table}\x00${row.column}`));
1138
+ return columns.find(({ table, column }) => !present.has(`${table}\x00${column}`));
1139
+ });
1140
+ var requireDeclaredObjectsExist = (pg, declared) => Effect12.gen(function* () {
1141
+ const missingTable = yield* findMissingTable(pg, declared.schema, declared.tables.map((table) => table.table));
1142
+ if (missingTable !== undefined) {
1143
+ return yield* Effect12.fail(new PostgresGrantsTableMissing({ schema: declared.schema, table: missingTable }));
1144
+ }
1145
+ const missingColumn = yield* findMissingColumn(pg, declared.schema, declared.columns.map((column) => ({ table: column.table, column: column.column })));
1146
+ if (missingColumn !== undefined) {
1147
+ return yield* Effect12.fail(new PostgresGrantsColumnMissing({
1148
+ schema: declared.schema,
1149
+ table: missingColumn.table,
1150
+ column: missingColumn.column
1151
+ }));
1152
+ }
1153
+ });
1154
+
1155
+ // src/postgres/grants-read.ts
1156
+ import * as Effect15 from "effect/Effect";
1157
+
1158
+ // src/postgres/grants-ownership.ts
1159
+ import * as Effect13 from "effect/Effect";
1160
+ var SCHEMA_OWNER_SQL = `SELECT
1161
+ n.nspowner = (SELECT oid FROM pg_roles WHERE rolname = $2) AS role_owns
1162
+ FROM pg_namespace n
1163
+ WHERE n.nspname = $1`;
1164
+ var readSchemaOwnership = (pg, schema, role) => Effect13.map(pg.unsafe(SCHEMA_OWNER_SQL, [schema, role]), (rows) => rows[0]?.role_owns === true);
1165
+ var OWNED_TABLES_SQL = `SELECT c.relname AS table
1166
+ FROM pg_class c
1167
+ WHERE c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)
1168
+ AND c.relowner = (SELECT oid FROM pg_roles WHERE rolname = $2)
1169
+ ORDER BY c.relname`;
1170
+ var readOwnedTables = (pg, schema, role) => Effect13.map(pg.unsafe(OWNED_TABLES_SQL, [schema, role]), (rows) => rows.map((row) => row.table).sort((a, b) => a.localeCompare(b)));
1171
+
1172
+ // src/postgres/grants-read-defaults.ts
1173
+ import * as Effect14 from "effect/Effect";
1174
+ var DEFAULTS_SQL = `SELECT
1175
+ pg_get_userbyid(d.defaclrole) AS for_role,
1176
+ a.privilege_type AS privilege,
1177
+ a.is_grantable AS grantable
1178
+ FROM pg_default_acl d
1179
+ CROSS JOIN LATERAL aclexplode(d.defaclacl) AS a
1180
+ WHERE d.defaclnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)
1181
+ AND d.defaclobjtype = 'r'
1182
+ AND a.grantee = (SELECT oid FROM pg_roles WHERE rolname = $2)
1183
+ ORDER BY for_role`;
1184
+ var readDefaultAcls = (pg, schema, role) => Effect14.map(pg.unsafe(DEFAULTS_SQL, [schema, role]), (rows) => {
1185
+ const byRole = new Map;
1186
+ for (const row of rows) {
1187
+ const words = byRole.get(row.for_role) ?? [];
1188
+ words.push(encodeWord(row));
1189
+ byRole.set(row.for_role, words);
1190
+ }
1191
+ return [...byRole.entries()].map(([forRole, words]) => ({ forRole, role: [...new Set(words)].sort() })).sort((a, b) => a.forRole.localeCompare(b.forRole));
1192
+ });
1193
+
1194
+ // src/postgres/grants-read.ts
1195
+ var SCHEMA_PRESENT_SQL = "SELECT 1 AS present FROM pg_namespace WHERE nspname = $1";
1196
+ var schemaExists = (pg, schema) => Effect15.map(pg.unsafe(SCHEMA_PRESENT_SQL, [schema]), (rows) => rows.length > 0);
1197
+ var SCHEMA_ACL_SQL = `SELECT
1198
+ a.grantee = 0 AS public,
1199
+ a.privilege_type AS privilege,
1200
+ a.is_grantable AS grantable
1201
+ FROM pg_namespace n
1202
+ CROSS JOIN LATERAL aclexplode(n.nspacl) AS a
1203
+ WHERE n.nspname = $1
1204
+ AND (a.grantee = 0 OR a.grantee = (SELECT oid FROM pg_roles WHERE rolname = $2))`;
1205
+ var readSchemaAcl = (pg, schema, role) => Effect15.map(pg.unsafe(SCHEMA_ACL_SQL, [schema, role]), (rows) => ({
1206
+ role: wordsOf(rows, false),
1207
+ public: wordsOf(rows, true)
1208
+ }));
1209
+ var TABLES_SQL = `SELECT
1210
+ c.relname AS table,
1211
+ c.relkind::text AS relkind,
1212
+ a.grantee = 0 AS public,
1213
+ a.privilege_type AS privilege,
1214
+ a.is_grantable AS grantable
1215
+ FROM pg_class c
1216
+ CROSS JOIN LATERAL aclexplode(c.relacl) AS a
1217
+ WHERE c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)
1218
+ AND (a.grantee = 0 OR a.grantee = (SELECT oid FROM pg_roles WHERE rolname = $2))
1219
+ ORDER BY c.relname`;
1220
+ var readTableAcls = (pg, schema, role) => Effect15.map(pg.unsafe(TABLES_SQL, [
1221
+ schema,
1222
+ role
1223
+ ]), (rows) => {
1224
+ const byTable = new Map;
1225
+ for (const row of rows) {
1226
+ const entry = byTable.get(row.table) ?? { relkind: row.relkind, role: [], public: [] };
1227
+ entry[row.public ? "public" : "role"].push(encodeWord(row));
1228
+ byTable.set(row.table, entry);
1229
+ }
1230
+ return [...byTable.entries()].map(([table, entry]) => ({
1231
+ table,
1232
+ relkind: entry.relkind,
1233
+ role: [...new Set(entry.role)].sort(),
1234
+ public: [...new Set(entry.public)].sort()
1235
+ })).sort((a, b) => a.table.localeCompare(b.table));
1236
+ });
1237
+ var COLUMNS_SQL = `SELECT
1238
+ c.relname AS table,
1239
+ v.attname AS column,
1240
+ a.grantee = 0 AS public,
1241
+ a.privilege_type AS privilege,
1242
+ a.is_grantable AS grantable,
1243
+ pg_get_userbyid(a.grantor) AS grantor,
1244
+ pg_get_userbyid(c.relowner) AS owner,
1245
+ current_user AS revoker,
1246
+ pg_has_role(current_user, c.relowner, 'USAGE') AS revoker_as_owner
1247
+ FROM pg_attribute v
1248
+ JOIN pg_class c ON c.oid = v.attrelid
1249
+ CROSS JOIN LATERAL aclexplode(v.attacl) AS a
1250
+ WHERE c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)
1251
+ AND v.attnum > 0
1252
+ AND NOT v.attisdropped
1253
+ AND (a.grantee = 0 OR a.grantee = (SELECT oid FROM pg_roles WHERE rolname = $2))
1254
+ ORDER BY c.relname, v.attname`;
1255
+ var readColumnAcls = (pg, schema, role) => Effect15.map(pg.unsafe(COLUMNS_SQL, [schema, role]), (rows) => {
1256
+ const byColumn = new Map;
1257
+ for (const row of rows) {
1258
+ const key = `${row.table}\x00${row.column}`;
1259
+ const entry = byColumn.get(key) ?? { role: [], public: [], restorable: [] };
1260
+ const word = encodeWord(row);
1261
+ entry[row.public ? "public" : "role"].push(word);
1262
+ if (!row.public && (row.grantor === row.revoker || row.grantor === row.owner && row.revoker_as_owner)) {
1263
+ entry.restorable.push(word);
1264
+ }
1265
+ byColumn.set(key, entry);
1266
+ }
1267
+ return [...byColumn.entries()].map(([key, entry]) => {
1268
+ const [table, column] = key.split("\x00");
1269
+ return {
1270
+ table,
1271
+ column,
1272
+ role: [...new Set(entry.role)].sort(),
1273
+ public: [...new Set(entry.public)].sort(),
1274
+ restorable: [...new Set(entry.restorable)].sort()
1275
+ };
1276
+ }).sort((a, b) => a.table.localeCompare(b.table) || a.column.localeCompare(b.column));
1277
+ });
1278
+ var readGrants = (pg, schema, role) => Effect15.all({
1279
+ schemaExists: schemaExists(pg, schema),
1280
+ schema: readSchemaAcl(pg, schema, role),
1281
+ tables: readTableAcls(pg, schema, role),
1282
+ columns: readColumnAcls(pg, schema, role),
1283
+ defaults: readDefaultAcls(pg, schema, role),
1284
+ schemaOwnedByRole: readSchemaOwnership(pg, schema, role),
1285
+ ownedTables: readOwnedTables(pg, schema, role)
1286
+ });
1287
+
1288
+ // src/postgres/grants-ops.ts
1289
+ var failRefusal = (refusal) => refusal.kind === "privilege" ? Effect16.fail(new PostgresGrantsPrivilegeRefused({ prop: refusal.prop, word: refusal.word })) : Effect16.fail(new PostgresGrantsDuplicateObject({ prop: refusal.prop, name: refusal.name }));
1290
+ var proveDatabase = (pg, declared) => Effect16.flatMap(currentDatabase(pg), (connected) => connected === declared ? Effect16.void : Effect16.fail(new PostgresGrantsDatabaseMismatch({ declared, connected })));
1291
+ var refuseNames = (props) => {
1292
+ const declared = resolveProps(props);
1293
+ const names = grantsNamesRefusal(declared);
1294
+ return names === undefined ? undefined : new PostgresGrantsNameRefused({
1295
+ name: names.name,
1296
+ byteLength: names.byteLength,
1297
+ limit: names.limit
1298
+ });
1299
+ };
1300
+ var reconcileWithClient3 = (pg, props, output) => Effect16.gen(function* () {
1301
+ const declared = resolveProps(props);
1302
+ const declRefusal = declarationRefusal(declared);
1303
+ if (declRefusal !== undefined)
1304
+ return yield* failRefusal(declRefusal);
1305
+ const nameRefusal = refuseNames(props);
1306
+ if (nameRefusal !== undefined)
1307
+ return yield* Effect16.fail(nameRefusal);
1308
+ for (const [prop, from, to] of [
1309
+ ["role", output?.role, declared.role],
1310
+ ["database", output?.database, declared.database],
1311
+ ["schema", output?.schema, declared.schema]
1312
+ ]) {
1313
+ if (from !== undefined && from !== to) {
1314
+ return yield* Effect16.fail(new PostgresGrantsRetargetRefused({ prop, from, to }));
1315
+ }
1316
+ }
1317
+ yield* proveDatabase(pg, declared.database);
1318
+ if (!(yield* schemaExists(pg, declared.schema))) {
1319
+ return yield* Effect16.fail(new PostgresGrantsSchemaMissing({ schema: declared.schema }));
1320
+ }
1321
+ for (const role of [declared.role, ...declared.defaults.map((entry) => entry.forRole)]) {
1322
+ if (!(yield* roleExists(pg, role))) {
1323
+ return yield* Effect16.fail(new PostgresGrantsRoleMissing({ role }));
1324
+ }
1325
+ }
1326
+ yield* requireDeclaredObjectsExist(pg, declared);
1327
+ const removed = removedNames(output, declared);
1328
+ const current = yield* readGrants(pg, declared.schema, declared.role);
1329
+ const planned = planReconcile(declared, current, removed);
1330
+ if (planned.length > 0)
1331
+ yield* pg.transaction(planned);
1332
+ const after = yield* readGrants(pg, declared.schema, declared.role);
1333
+ const remaining = planReconcile(declared, after, removed);
1334
+ if (remaining.length > 0) {
1335
+ return yield* Effect16.fail(new PostgresGrantsRepairRefused({
1336
+ schema: declared.schema,
1337
+ role: declared.role,
1338
+ remaining
1339
+ }));
1340
+ }
1341
+ return attributesOf(after, declaredNames(declared));
1342
+ });
1343
+ var readWithClient3 = (pg, names, stored) => Effect16.gen(function* () {
1344
+ yield* proveDatabase(pg, names.database);
1345
+ if (!(yield* schemaExists(pg, names.schema)))
1346
+ return;
1347
+ const live = yield* readGrants(pg, names.schema, names.role);
1348
+ const projected = attributesOf(live, names);
1349
+ const holdsAnything = projected.schemaPrivileges.length > 0 || projected.tables.some((table) => table.privileges.length > 0) || projected.columns.some((column) => column.privileges.length > 0) || projected.defaults.some((entry) => entry.privileges.length > 0);
1350
+ return stored || holdsAnything ? projected : undefined;
1351
+ });
1352
+ var deleteWithClient2 = (pg, props) => Effect16.gen(function* () {
1353
+ yield* proveDatabase(pg, props.database);
1354
+ if (!(yield* roleExists(pg, props.role)))
1355
+ return;
1356
+ if (!(yield* schemaExists(pg, props.schema)))
1357
+ return;
1358
+ const cleared = clearedDeclaration(resolveProps(props));
1359
+ const live = yield* readGrants(pg, cleared.schema, cleared.role);
1360
+ const planned = planRepair(cleared, live);
1361
+ if (planned.length > 0)
1362
+ yield* pg.transaction(planned);
1363
+ const after = yield* readGrants(pg, cleared.schema, cleared.role);
1364
+ const remaining = planRepair(cleared, after);
1365
+ if (remaining.length > 0) {
1366
+ return yield* Effect16.fail(new PostgresGrantsRepairRefused({
1367
+ schema: cleared.schema,
1368
+ role: cleared.role,
1369
+ remaining
1370
+ }));
1371
+ }
1372
+ });
1373
+ var diffPostgresGrants = (news, output) => Effect16.gen(function* () {
1374
+ if (output === undefined || !isResolved3(news))
1375
+ return;
1376
+ const declared = resolveProps(news);
1377
+ for (const [prop, from, to] of [
1378
+ ["role", output.role, declared.role],
1379
+ ["database", output.database, declared.database],
1380
+ ["schema", output.schema, declared.schema]
1381
+ ]) {
1382
+ if (from !== to) {
1383
+ return yield* Effect16.fail(new PostgresGrantsRetargetRefused({ prop, from, to }));
1384
+ }
1385
+ }
1386
+ const nameRefusal = refuseNames(news);
1387
+ if (nameRefusal !== undefined)
1388
+ return yield* Effect16.fail(nameRefusal);
1389
+ const declRefusal = declarationRefusal(declared);
1390
+ if (declRefusal !== undefined)
1391
+ return yield* failRefusal(declRefusal);
1392
+ return grantsDiffer(declared, output) ? { action: "update" } : { action: "noop" };
1393
+ });
1394
+
1395
+ // src/postgres/grants.ts
1396
+ var PostgresGrants = Resource3("Postgres.Grants", {
1397
+ defaultRemovalPolicy: "retain"
1398
+ });
1399
+ var isPostgresGrants = (value) => (typeof value === "object" || typeof value === "function") && value !== null && value.Type === "Postgres.Grants";
1400
+ var postgresGrantsHandlers = PostgresGrants.Provider.of({
1401
+ list: () => Effect17.succeed([]),
1402
+ nuke: { skip: true },
1403
+ read: ({ olds, output }) => Effect17.gen(function* () {
1404
+ const names = output !== undefined ? namesFromAttrs(output) : declaredNames(resolveProps(olds));
1405
+ const present = yield* withPg((pg) => databaseExists(pg, names.database));
1406
+ if (!present)
1407
+ return;
1408
+ const live = yield* withPg((pg) => readWithClient3(pg, names, output !== undefined), names.database);
1409
+ return live === undefined ? undefined : Unowned3(live);
1410
+ }),
1411
+ diff: ({ news, output }) => diffPostgresGrants(news, output),
1412
+ reconcile: ({ news, output }) => Effect17.gen(function* () {
1413
+ const present = yield* withPg((pg) => databaseExists(pg, news.database));
1414
+ if (!present) {
1415
+ return yield* Effect17.fail(new PostgresGrantsDatabaseMissing({ database: news.database }));
1416
+ }
1417
+ return yield* withPg((pg) => reconcileWithClient3(pg, news, output), news.database);
1418
+ }),
1419
+ delete: ({ olds }) => Effect17.gen(function* () {
1420
+ const present = yield* withPg((pg) => databaseExists(pg, olds.database));
1421
+ if (!present)
1422
+ return;
1423
+ return yield* withPg((pg) => deleteWithClient2(pg, olds), olds.database);
1424
+ })
1425
+ });
1426
+ var PostgresGrantsProvider = () => Provider3.succeed(PostgresGrants, postgresGrantsHandlers);
1427
+ // src/postgres/role-attrs.ts
1428
+ var VALID_UNTIL_OFFSET = /(?:Z|[+-]\d{2}:\d{2})$/;
1429
+ var validUntilRefusal = (value) => {
1430
+ if (Number.isNaN(Date.parse(value)))
1431
+ return { reason: "unparseable" };
1432
+ if (VALID_UNTIL_OFFSET.test(value))
1433
+ return;
1434
+ return { reason: "zone-free" };
1435
+ };
1436
+ var sameValidUntil = (declared, live) => live !== null && Date.parse(declared) === Date.parse(live);
1437
+ // src/postgres/role-sql.ts
1438
+ import * as Effect19 from "effect/Effect";
1439
+
1440
+ // src/postgres/role-membership-sql.ts
1441
+ import * as Effect18 from "effect/Effect";
1442
+
1443
+ // src/postgres/role-errors.ts
1444
+ import * as Data4 from "effect/Data";
1445
+
1446
+ class PostgresRoleNameRefused extends Data4.TaggedError("PostgresRoleNameRefused") {
1447
+ get message() {
1448
+ return `Postgres.Role "${this.name}": ${String(this.byteLength)} UTF-8 bytes, over the ` + `${String(this.limit)}-byte limit (NAMEDATALEN 64, minus the terminator). PostgreSQL would ` + "accept the CREATE and silently truncate the name with only a NOTICE, so this plan refuses " + "it instead of creating a role no later read will ever find again.";
1449
+ }
1450
+ }
1451
+
1452
+ class PostgresRoleRenameRefused extends Data4.TaggedError("PostgresRoleRenameRefused") {
1453
+ get message() {
1454
+ return `Postgres.Role: the declared name changed from "${this.from}" to "${this.to}". A ` + "rename is refused at plan — this provider never replaces a role (that is DROP then " + "CREATE, which loses memberships, grants and ownerships). Declare a new logical id for " + "the new name, and remove the old one once its objects have moved.";
1455
+ }
1456
+ }
1457
+
1458
+ class PostgresRolePasswordEnvUnsetError extends Data4.TaggedError("PostgresRolePasswordEnvUnsetError") {
1459
+ get message() {
1460
+ return `Postgres.Role "${this.role}": password.fromEnv "${this.variable}" is unset or empty ` + "in the deploying process, but this write requires a password this stack has not sealed.";
1461
+ }
1462
+ }
1463
+
1464
+ class PostgresRolePrivilegedRefused extends Data4.TaggedError("PostgresRolePrivilegedRefused") {
1465
+ get message() {
1466
+ return `Postgres.Role "${this.role}": the live role has ${this.flags.join(", ")} set. This ` + "family never declares those attributes (a create lands on the server default, all off), " + "and they are not inherited — a member who SET ROLEs to this role would exercise them. " + "Clear the flag on the cluster before this stack adopts the role.";
1467
+ }
1468
+ }
1469
+
1470
+ class PostgresRoleValidUntilRefused extends Data4.TaggedError("PostgresRoleValidUntilRefused") {
1471
+ get message() {
1472
+ return `Postgres.Role "${this.role}": validUntil "${this.value}" is ${this.reason === "unparseable" ? "not a timestamp this kit can parse" : "missing its UTC designator or numeric offset"}. ` + 'Declare an explicit-zone ISO 8601 value such as "2027-01-01T00:00:00Z" — PostgreSQL would ' + "read a zone-free value in the session time zone while the plan compares UTC, and the two " + "would never agree.";
1473
+ }
1474
+ }
1475
+
1476
+ class PostgresRoleCreateVanished extends Data4.TaggedError("PostgresRoleCreateVanished") {
1477
+ get message() {
1478
+ return `Postgres.Role "${this.role}": CREATE ROLE raised no error, but the role is still ` + "absent on the immediate re-read. Re-plan — this is not a state this provider expects to reach.";
1479
+ }
1480
+ }
1481
+
1482
+ class PostgresRoleMembershipUnrepaired extends Data4.TaggedError("PostgresRoleMembershipUnrepaired") {
1483
+ get message() {
1484
+ const who = this.grantor === null ? "a grantor role that no longer exists" : `grantor "${this.grantor}"`;
1485
+ return `Postgres.Role "${this.role}": membership of "${this.parent}" ` + `(${this.declared ? "declared, options unsafe" : "not declared"}) could not be repaired ` + `after its statements — the row still stands under ${who}. pg_auth_members keys a ` + "membership on (parent, member, grantor), and a grantor that no longer exists cannot be " + "named in REVOKE … GRANTED BY. Revoke the row by hand as a bootstrap superuser " + "(`REVOKE role FROM member GRANTED BY <grantor>`; a grantor oid with no role name needs " + "catalog surgery) and re-plan.";
1486
+ }
1487
+ }
1488
+
1489
+ class PostgresRoleParentMissing extends Data4.TaggedError("PostgresRoleParentMissing") {
1490
+ get message() {
1491
+ return `Postgres.Role "${this.role}": memberOf parent "${this.parent}" does not exist. ` + "Nothing was written — declare that role first (or drop it from memberOf). A GRANT of a " + "missing role fails only after CREATE ROLE has committed, and the next plan then needs " + "--adopt to finish the orphan.";
1492
+ }
1493
+ }
1494
+
1495
+ class PostgresRoleIdentityRefused extends Data4.TaggedError("PostgresRoleIdentityRefused") {
1496
+ get message() {
1497
+ return `Postgres.Role "${this.role}": the live oid is ${String(this.liveOid)}, but state recorded ` + `${String(this.storedOid)}. The name was dropped and recreated outside this stack. Nothing ` + "was altered and the live role was not dropped — remove the stale state, or adopt the new " + "role under a new logical id once you mean to own it.";
1498
+ }
1499
+ }
1500
+
1501
+ // src/postgres/role-membership-sql.ts
1502
+ var MEMBERSHIP_SQL = `SELECT parent.rolname AS parent,
1503
+ grantor.rolname AS grantor,
1504
+ m.admin_option AS admin,
1505
+ m.set_option AS set
1506
+ FROM pg_auth_members m
1507
+ JOIN pg_roles member ON member.oid = m.member
1508
+ JOIN pg_roles parent ON parent.oid = m.roleid
1509
+ LEFT JOIN pg_roles grantor ON grantor.oid = m.grantor
1510
+ WHERE member.rolname = $1
1511
+ ORDER BY parent.rolname, grantor.rolname NULLS FIRST`;
1512
+ var selectRoleMemberships = (pg, name) => Effect18.map(pg.unsafe(MEMBERSHIP_SQL, [name]), (rows) => rows);
1513
+ var buildGrantMembershipSql = (member, parent) => `GRANT ${quoteIdent(parent)} TO ${quoteIdent(member)} WITH SET FALSE`;
1514
+ var buildRepairMembershipSql = (member, parent) => `GRANT ${quoteIdent(parent)} TO ${quoteIdent(member)} WITH ADMIN FALSE, SET FALSE`;
1515
+ var buildRevokeGrantorMembershipSql = (member, parent, grantor) => `REVOKE ${quoteIdent(parent)} FROM ${quoteIdent(member)} GRANTED BY ${quoteIdent(grantor)}`;
1516
+ var unsafeMemberships = (live) => new Set((live.memberships ?? []).filter((row) => row.admin || row.set).map((row) => row.parent));
1517
+ var membershipDrift = (declared, live, unsafe = new Set) => {
1518
+ if (declared === undefined)
1519
+ return { grants: [], revokes: [] };
1520
+ const wanted = [...new Set(declared)].sort();
1521
+ const current = [...new Set(live)].sort();
1522
+ return {
1523
+ grants: wanted.filter((parent) => !current.includes(parent) || unsafe.has(parent)),
1524
+ revokes: [
1525
+ ...current.filter((parent) => !wanted.includes(parent)),
1526
+ ...wanted.filter((parent) => current.includes(parent) && unsafe.has(parent))
1527
+ ]
1528
+ };
1529
+ };
1530
+ var assertParentsExist = (pg, role, parents) => Effect18.gen(function* () {
1531
+ if (parents === undefined)
1532
+ return;
1533
+ for (const parent of [...new Set(parents)].sort()) {
1534
+ if (!(yield* roleExists(pg, parent))) {
1535
+ return yield* Effect18.fail(new PostgresRoleParentMissing({ role, parent }));
1536
+ }
1537
+ }
1538
+ });
1539
+ var syncMemberships = (pg, props, rows) => Effect18.gen(function* () {
1540
+ if (props.memberOf === undefined) {
1541
+ return [...new Set(rows.map((row) => row.parent))].sort();
1542
+ }
1543
+ yield* assertParentsExist(pg, props.name, props.memberOf);
1544
+ const wanted = [...new Set(props.memberOf)].sort();
1545
+ for (const row of rows) {
1546
+ if (wanted.includes(row.parent) || row.grantor === null)
1547
+ continue;
1548
+ yield* pg.unsafe(buildRevokeGrantorMembershipSql(props.name, row.parent, row.grantor)).pipe(Effect18.asVoid);
1549
+ }
1550
+ const repaired = [
1551
+ ...new Set(rows.filter((row) => wanted.includes(row.parent) && (row.admin || row.set) && row.grantor !== null).map((row) => row.parent))
1552
+ ];
1553
+ for (const parent of repaired) {
1554
+ yield* pg.unsafe(buildRepairMembershipSql(props.name, parent)).pipe(Effect18.asVoid);
1555
+ }
1556
+ if (repaired.length > 0) {
1557
+ const midRead = yield* selectRoleMemberships(pg, props.name);
1558
+ for (const row of midRead) {
1559
+ if (wanted.includes(row.parent) && (row.admin || row.set) && row.grantor !== null) {
1560
+ yield* pg.unsafe(buildRevokeGrantorMembershipSql(props.name, row.parent, row.grantor)).pipe(Effect18.asVoid);
1561
+ }
1562
+ }
1563
+ }
1564
+ for (const parent of wanted.filter((p) => !rows.some((row) => row.parent === p))) {
1565
+ yield* pg.unsafe(buildGrantMembershipSql(props.name, parent)).pipe(Effect18.asVoid);
1566
+ }
1567
+ for (const row of yield* selectRoleMemberships(pg, props.name)) {
1568
+ if (!wanted.includes(row.parent) || row.admin || row.set) {
1569
+ return yield* Effect18.fail(new PostgresRoleMembershipUnrepaired({
1570
+ role: props.name,
1571
+ parent: row.parent,
1572
+ grantor: row.grantor,
1573
+ declared: wanted.includes(row.parent)
1574
+ }));
1575
+ }
1576
+ }
1577
+ return wanted;
1578
+ });
1579
+
1580
+ // src/postgres/role-sql.ts
1581
+ var SELECT_ROLE_SQL = `SELECT
1582
+ r.oid AS oid,
1583
+ r.rolname AS name,
1584
+ r.rolcanlogin AS login,
1585
+ r.rolconnlimit AS "connectionLimit",
1586
+ r.rolinherit AS inherit,
1587
+ r.rolsuper AS superuser,
1588
+ r.rolcreaterole AS createrole,
1589
+ r.rolcreatedb AS createdb,
1590
+ r.rolreplication AS replication,
1591
+ r.rolbypassrls AS bypassrls,
1592
+ to_char(r.rolvaliduntil AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"') AS "validUntil"
1593
+ FROM pg_roles r
1594
+ WHERE r.rolname = $1`;
1595
+ var selectRole = (pg, name) => Effect19.map(pg.unsafe(SELECT_ROLE_SQL, [name]), (rows) => rows[0]);
1596
+ var readRoleWithClient = (pg, name) => Effect19.gen(function* () {
1597
+ const row = yield* selectRole(pg, name);
1598
+ if (row === undefined)
1599
+ return;
1600
+ const memberships = yield* selectRoleMemberships(pg, name);
1601
+ return {
1602
+ ...row,
1603
+ memberOf: [...new Set(memberships.map((membership) => membership.parent))].sort(),
1604
+ memberships
1605
+ };
1606
+ });
1607
+ var withOptions2 = (props) => {
1608
+ const parts = [];
1609
+ parts.push(props.login ? "LOGIN" : "NOLOGIN");
1610
+ parts.push(props.inherit ? "INHERIT" : "NOINHERIT");
1611
+ parts.push(`CONNECTION LIMIT ${String(Math.trunc(props.connectionLimit))}`);
1612
+ if (props.validUntil !== undefined) {
1613
+ parts.push(`VALID UNTIL ${quoteStringLiteral(props.validUntil)}`);
1614
+ }
1615
+ return parts.join(" ");
1616
+ };
1617
+ var buildCreateRoleSql = (props) => `CREATE ROLE ${quoteIdent(props.name)} WITH ${withOptions2(props)}`;
1618
+ var buildAlterRoleSql = (name, change) => {
1619
+ const who = quoteIdent(name);
1620
+ switch (change.prop) {
1621
+ case "login":
1622
+ return `ALTER ROLE ${who} WITH ${change.value ? "LOGIN" : "NOLOGIN"}`;
1623
+ case "inherit":
1624
+ return `ALTER ROLE ${who} WITH ${change.value ? "INHERIT" : "NOINHERIT"}`;
1625
+ case "connectionLimit":
1626
+ return `ALTER ROLE ${who} WITH CONNECTION LIMIT ${String(Math.trunc(change.value))}`;
1627
+ case "validUntil":
1628
+ return `ALTER ROLE ${who} WITH VALID UNTIL ${quoteStringLiteral(change.value)}`;
1629
+ }
1630
+ };
1631
+ var buildSetPasswordSql = (name, verifier) => `ALTER ROLE ${quoteIdent(name)} WITH PASSWORD ${quoteStringLiteral(verifier)}`;
1632
+ var buildDropRoleSql = (name) => `DROP ROLE IF EXISTS ${quoteIdent(name)}`;
1633
+ var buildCreateStatements = (props, verifier) => [
1634
+ buildCreateRoleSql(props),
1635
+ ...verifier === undefined ? [] : [buildSetPasswordSql(props.name, verifier)],
1636
+ ...[...new Set(props.memberOf ?? [])].sort().map((parent) => buildGrantMembershipSql(props.name, parent))
1637
+ ];
1638
+ var scalarDrift = (props, live) => {
1639
+ const changes = [];
1640
+ if (props.login !== live.login)
1641
+ changes.push({ prop: "login", value: props.login });
1642
+ if (props.inherit !== live.inherit)
1643
+ changes.push({ prop: "inherit", value: props.inherit });
1644
+ if (props.connectionLimit !== live.connectionLimit) {
1645
+ changes.push({ prop: "connectionLimit", value: props.connectionLimit });
1646
+ }
1647
+ if (props.validUntil !== undefined && !sameValidUntil(props.validUntil, live.validUntil)) {
1648
+ changes.push({ prop: "validUntil", value: props.validUntil });
1649
+ }
1650
+ return changes;
1651
+ };
1652
+ var declarationMatches = (props, live) => {
1653
+ const wanted = props.memberOf === undefined ? undefined : [...new Set(props.memberOf)].sort().join("\x00");
1654
+ const found = [...live.memberOf ?? []].sort().join("\x00");
1655
+ return props.name === live.name && props.login === live.login && props.inherit === live.inherit && props.connectionLimit === live.connectionLimit && (props.validUntil === undefined || sameValidUntil(props.validUntil, live.validUntil)) && (wanted === undefined || wanted === found) && privilegedFlags(live).length === 0 && (live.memberships ?? []).every((row) => !row.admin && !row.set);
1656
+ };
1657
+ var storedAttributes = (live) => {
1658
+ const { memberships: _observeOnly, ...stored } = live;
1659
+ return stored;
1660
+ };
1661
+ var privilegedFlags = (live) => {
1662
+ const flags = [];
1663
+ if (live.superuser === true)
1664
+ flags.push("SUPERUSER");
1665
+ if (live.createrole === true)
1666
+ flags.push("CREATEROLE");
1667
+ if (live.createdb === true)
1668
+ flags.push("CREATEDB");
1669
+ if (live.replication === true)
1670
+ flags.push("REPLICATION");
1671
+ if (live.bypassrls === true)
1672
+ flags.push("BYPASSRLS");
1673
+ return flags;
1674
+ };
1675
+ // src/postgres/role.ts
1676
+ import { Resource as Resource4 } from "alchemy";
1677
+ import * as Effect20 from "effect/Effect";
1678
+ import * as Redacted2 from "effect/Redacted";
1679
+
1680
+ // src/postgres/role-secrets.ts
1681
+ import * as Redacted from "effect/Redacted";
1682
+
1683
+ // src/secrets/write-only.ts
1684
+ import { Buffer } from "node:buffer";
1685
+ import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
1686
+ var resolveAll = (refs, env = process.env) => {
1687
+ const values = {};
1688
+ const missing = [];
1689
+ for (const [key, ref] of Object.entries(refs)) {
1690
+ const value = env[ref.fromEnv];
1691
+ if (value === undefined || value === "")
1692
+ missing.push(ref.fromEnv);
1693
+ else
1694
+ values[key] = value;
1695
+ }
1696
+ return { missing: [...new Set(missing)].sort(), values };
1697
+ };
1698
+ var COST = { N: 16384, maxmem: 64 * 1024 * 1024, p: 1, r: 8 };
1699
+ var PREFIX = "scrypt";
1700
+ var canonical = (values) => JSON.stringify(Object.keys(values).sort().map((key) => [key, values[key]]));
1701
+ var digest = (salt, values) => scryptSync(canonical(values), salt, 32, COST);
1702
+ var seal = (values, salt) => {
1703
+ if (Object.keys(values).length === 0)
1704
+ return "";
1705
+ const bytes = salt === undefined ? randomBytes(16) : Buffer.from(salt, "utf8");
1706
+ return `${PREFIX}:${bytes.toString("base64url")}:${digest(bytes, values).toString("base64url")}`;
1707
+ };
1708
+ var sealMatches = (sealed, values) => {
1709
+ if (sealed === "")
1710
+ return Object.keys(values).length === 0;
1711
+ const [prefix, salt, stored, extra] = sealed.split(":");
1712
+ if (prefix !== PREFIX || salt === undefined || stored === undefined || extra !== undefined) {
1713
+ return false;
1714
+ }
1715
+ const expected = Buffer.from(stored, "base64url");
1716
+ const actual = digest(Buffer.from(salt, "base64url"), values);
1717
+ return expected.length === actual.length && timingSafeEqual(expected, actual);
1718
+ };
1719
+
1720
+ // src/postgres/role-secrets.ts
1721
+ var resolvePassword = (props, env = process.env) => {
1722
+ if (props.password === undefined)
1723
+ return { variable: undefined, value: undefined };
1724
+ const { values } = resolveAll({ password: props.password }, env);
1725
+ const raw = values["password"];
1726
+ return {
1727
+ variable: props.password.fromEnv,
1728
+ value: raw === undefined ? undefined : Redacted.make(raw)
1729
+ };
1730
+ };
1731
+ var sealPassword = (value2) => seal({ password: Redacted.value(value2) });
1732
+ var passwordMatchesSeal = (value2, sealed) => sealMatches(sealed, { password: Redacted.value(value2) });
1733
+
1734
+ // src/postgres/role-scram.ts
1735
+ import { createHash, createHmac, pbkdf2Sync, randomBytes as randomBytes2 } from "node:crypto";
1736
+ var ITERATIONS = 4096;
1737
+ var SALT_BYTES = 16;
1738
+ var KEY_BYTES = 32;
1739
+ var hmac = (key, label) => createHmac("sha256", key).update(label, "utf8").digest();
1740
+ var b64 = (value2) => value2.toString("base64");
1741
+ var scramSha256Verifier = (password, salt) => {
1742
+ const bytes = salt ?? randomBytes2(SALT_BYTES);
1743
+ const saltedPassword = pbkdf2Sync(password, bytes, ITERATIONS, KEY_BYTES, "sha256");
1744
+ const storedKey = createHash("sha256").update(hmac(saltedPassword, "Client Key")).digest();
1745
+ const serverKey = hmac(saltedPassword, "Server Key");
1746
+ return `SCRAM-SHA-256$${ITERATIONS}:${b64(bytes)}$${b64(storedKey)}:${b64(serverKey)}`;
1747
+ };
1748
+
1749
+ // src/postgres/role.ts
1750
+ var PostgresRole = Resource4("Postgres.Role", {
1751
+ defaultRemovalPolicy: "retain"
1752
+ });
1753
+ var isPostgresRole = (value3) => (typeof value3 === "object" || typeof value3 === "function") && value3 !== null && value3.Type === "Postgres.Role";
1754
+ var reconcileWithClient4 = (pg, props, env = process.env, previousSeal = "", storedOid) => Effect20.gen(function* () {
1755
+ const resolved = resolvePassword(props, env);
1756
+ const observed = yield* readRoleWithClient(pg, props.name);
1757
+ if (observed === undefined) {
1758
+ if (resolved.variable !== undefined && resolved.value === undefined) {
1759
+ return yield* Effect20.fail(new PostgresRolePasswordEnvUnsetError({ role: props.name, variable: resolved.variable }));
1760
+ }
1761
+ yield* assertParentsExist(pg, props.name, props.memberOf);
1762
+ const verifier = resolved.value === undefined ? undefined : scramSha256Verifier(Redacted2.value(resolved.value));
1763
+ yield* pg.transaction(buildCreateStatements(props, verifier));
1764
+ const created = yield* readRoleWithClient(pg, props.name);
1765
+ if (created === undefined) {
1766
+ return yield* Effect20.fail(new PostgresRoleCreateVanished({ role: props.name }));
1767
+ }
1768
+ return {
1769
+ ...storedAttributes(created),
1770
+ passwordSeal: resolved.value === undefined ? "" : sealPassword(resolved.value)
1771
+ };
1772
+ }
1773
+ if (storedOid !== undefined && observed.oid !== storedOid) {
1774
+ return yield* Effect20.fail(new PostgresRoleIdentityRefused({
1775
+ role: props.name,
1776
+ storedOid,
1777
+ liveOid: observed.oid
1778
+ }));
1779
+ }
1780
+ const flags = privilegedFlags(observed);
1781
+ if (flags.length > 0) {
1782
+ return yield* Effect20.fail(new PostgresRolePrivilegedRefused({ role: props.name, flags }));
1783
+ }
1784
+ if (resolved.variable !== undefined && resolved.value === undefined && previousSeal === "") {
1785
+ return yield* Effect20.fail(new PostgresRolePasswordEnvUnsetError({ role: props.name, variable: resolved.variable }));
1786
+ }
1787
+ yield* assertParentsExist(pg, props.name, props.memberOf);
1788
+ for (const change of scalarDrift(props, observed)) {
1789
+ yield* pg.unsafe(buildAlterRoleSql(props.name, change)).pipe(Effect20.asVoid);
1790
+ }
1791
+ const passwordMatched = resolved.value !== undefined && passwordMatchesSeal(resolved.value, previousSeal);
1792
+ if (resolved.value !== undefined && !passwordMatched) {
1793
+ yield* pg.unsafe(buildSetPasswordSql(props.name, scramSha256Verifier(Redacted2.value(resolved.value)))).pipe(Effect20.asVoid);
1794
+ }
1795
+ yield* syncMemberships(pg, props, observed.memberships ?? []);
1796
+ const after = yield* readRoleWithClient(pg, props.name);
1797
+ if (after === undefined) {
1798
+ return yield* Effect20.fail(new PostgresRoleCreateVanished({ role: props.name }));
1799
+ }
1800
+ return {
1801
+ ...storedAttributes(after),
1802
+ passwordSeal: resolved.value === undefined || passwordMatched ? previousSeal : sealPassword(resolved.value)
1803
+ };
1804
+ });
1805
+ var readRole = (pg, name) => readRoleWithClient(pg, name);
1806
+ var refuseAtPlan = (props) => Effect20.gen(function* () {
1807
+ const nameRefusal = nameByteRefusal(props.name);
1808
+ if (nameRefusal !== undefined) {
1809
+ return yield* Effect20.fail(new PostgresRoleNameRefused({ name: props.name, ...nameRefusal }));
1810
+ }
1811
+ if (props.validUntil !== undefined) {
1812
+ const untilRefusal = validUntilRefusal(props.validUntil);
1813
+ if (untilRefusal !== undefined) {
1814
+ return yield* Effect20.fail(new PostgresRoleValidUntilRefused({
1815
+ role: props.name,
1816
+ value: props.validUntil,
1817
+ reason: untilRefusal.reason
1818
+ }));
1819
+ }
1820
+ }
1821
+ });
1822
+ // src/postgres/role-provider.ts
1823
+ import * as Provider4 from "alchemy/Provider";
1824
+ import { isResolved as isResolved5 } from "alchemy/Diff";
1825
+ import * as Effect27 from "effect/Effect";
1826
+
1827
+ // src/ownership/probe.ts
1828
+ import { Unowned as Unowned4 } from "alchemy/AdoptPolicy";
1829
+ import * as Effect23 from "effect/Effect";
1830
+
1831
+ // src/ownership/rows.ts
1832
+ import { Stack as Stack2 } from "alchemy/Stack";
1833
+ import { State, isActionState } from "alchemy/State/State";
1834
+ import * as Effect22 from "effect/Effect";
1835
+ import * as Option2 from "effect/Option";
1836
+
1837
+ // src/ownership/whole.ts
1838
+ import { Stack } from "alchemy/Stack";
1839
+ import * as Effect21 from "effect/Effect";
1840
+ import * as Option from "effect/Option";
1841
+ var absent = (value3) => value3 === undefined || value3 === null;
1842
+ var isPlainObject = (value3) => {
1843
+ if (typeof value3 !== "object" || value3 === null || Array.isArray(value3))
1844
+ return false;
1845
+ const proto = Object.getPrototypeOf(value3);
1846
+ return proto === Object.prototype || proto === null;
1847
+ };
1848
+ var carries = (declared, recorded) => {
1849
+ if (absent(declared))
1850
+ return true;
1851
+ if (absent(recorded))
1852
+ return false;
1853
+ if (Array.isArray(declared)) {
1854
+ if (!Array.isArray(recorded))
1855
+ return false;
1856
+ return declared.every((each, index) => carries(each, recorded[index]));
1857
+ }
1858
+ if (isPlainObject(declared)) {
1859
+ if (!isPlainObject(recorded))
1860
+ return false;
1861
+ return Object.entries(declared).every(([key, each]) => carries(each, recorded[key]));
1862
+ }
1863
+ return true;
1864
+ };
1865
+ var wholeDeclaration = (fqn, props) => Effect21.map(Effect21.serviceOption(Stack), (stack) => {
1866
+ const declared = Option.isSome(stack) ? stack.value.resources[fqn] : undefined;
1867
+ return declared !== undefined && carries(declared.Props, props);
1868
+ });
1869
+
1870
+ // src/ownership/rows.ts
1871
+ var storeOf = Effect22.gen(function* () {
1872
+ const stack = yield* Effect22.serviceOption(Stack2);
1873
+ const state = yield* Effect22.serviceOption(State);
1874
+ if (Option2.isNone(stack) || Option2.isNone(state))
1875
+ return;
1876
+ const store = yield* state.value;
1877
+ const where = { stack: stack.value.name, stage: stack.value.stage };
1878
+ return { resources: stack.value.resources, store, where };
1879
+ });
1880
+ var rowAt = (store, where, fqn) => store.get({ ...where, fqn }).pipe(Effect22.orElseSucceed(() => {
1881
+ return;
1882
+ }));
1883
+ var generationOf = (row, instanceId) => {
1884
+ let at = row;
1885
+ while (typeof at === "object" && at !== null && !isActionState(at)) {
1886
+ const generation = at;
1887
+ if (generation.instanceId === instanceId)
1888
+ return generation;
1889
+ at = generation.old;
1890
+ }
1891
+ return;
1892
+ };
1893
+ var recordedGeneration = (fqn, instanceId) => Effect22.gen(function* () {
1894
+ const found = yield* storeOf;
1895
+ if (found === undefined)
1896
+ return;
1897
+ for (const at of [fqn, ...found.resources[fqn]?.FormerFqns ?? []]) {
1898
+ const generation = generationOf(yield* rowAt(found.store, found.where, at), instanceId);
1899
+ if (generation !== undefined)
1900
+ return generation;
1901
+ }
1902
+ return;
1903
+ });
1904
+ var recordedInstance = (fqn, instanceId) => Effect22.gen(function* () {
1905
+ const generation = yield* recordedGeneration(fqn, instanceId);
1906
+ if (generation === undefined)
1907
+ return "absent";
1908
+ return (yield* wholeDeclaration(fqn, generation.props)) ? "whole" : "partial";
1909
+ });
1910
+ var isCreate = (fqn, instanceId) => Effect22.map(recordedGeneration(fqn, instanceId), (generation) => generation?.status === "creating");
1911
+ var idOf = (attr) => {
1912
+ if (typeof attr !== "object" || attr === null)
1913
+ return;
1914
+ const id = attr.id;
1915
+ return typeof id === "string" && id !== "" ? id : undefined;
1916
+ };
1917
+ var olderGenerationIds = (fqn, instanceId) => Effect22.gen(function* () {
1918
+ const found = yield* storeOf;
1919
+ if (found === undefined)
1920
+ return [];
1921
+ const ids = [];
1922
+ for (const at of [fqn, ...found.resources[fqn]?.FormerFqns ?? []]) {
1923
+ collectOlder(yield* rowAt(found.store, found.where, at), instanceId, ids);
1924
+ }
1925
+ return ids;
1926
+ });
1927
+ var collectOlder = (row, instanceId, ids) => {
1928
+ let at = row;
1929
+ let seen = false;
1930
+ while (typeof at === "object" && at !== null && !isActionState(at)) {
1931
+ const generation = at;
1932
+ if (!seen) {
1933
+ seen = generation.instanceId === instanceId;
1934
+ at = generation.old;
1935
+ continue;
1936
+ }
1937
+ if (generation.instanceId !== instanceId) {
1938
+ const id = idOf(generation.attr);
1939
+ if (id !== undefined)
1940
+ ids.push(id);
1941
+ }
1942
+ at = generation.old;
1943
+ }
1944
+ };
1945
+ var forgetRefusedCreate = (fqn, instanceId) => Effect22.gen(function* () {
1946
+ const found = yield* storeOf;
1947
+ if (found === undefined)
1948
+ return;
1949
+ const row = yield* rowAt(found.store, found.where, fqn);
1950
+ const mine = row;
1951
+ if (mine?.status !== "creating" || mine.instanceId !== instanceId || mine.attr !== undefined) {
1952
+ return;
1953
+ }
1954
+ yield* found.store.delete({ ...found.where, fqn }).pipe(Effect22.ignore);
1955
+ });
1956
+
1957
+ // src/ownership/probe.ts
1958
+ var provenOurs = (ask, settled) => Effect23.gen(function* () {
1959
+ const recorded = yield* recordedInstance(ask.fqn, ask.instanceId);
1960
+ if (recorded === "absent")
1961
+ return false;
1962
+ if (recorded === "partial") {
1963
+ yield* Effect23.logWarning(`${ask.fqn}: the interrupted create's state row lacks part of the declaration (a prop ` + "was still an Output when it was written), so the live object is not proven ours");
1964
+ return false;
1965
+ }
1966
+ const unproven = (reason) => Effect23.as(Effect23.logWarning(`${ask.fqn}: the interrupted create's object could not be compared with its props ` + `(${reason instanceof Error ? reason.message : String(reason)}), so it is not treated as ours`), false);
1967
+ return yield* settled.pipe(Effect23.catch(unproven), Effect23.catchDefect(unproven));
1968
+ });
1969
+ var ownedRead = (ask, found, settled) => Effect23.gen(function* () {
1970
+ if (found === undefined || ask.output !== undefined)
1971
+ return found;
1972
+ return (yield* provenOurs(ask, settled)) ? found : Unowned4(found);
1973
+ });
1974
+
1975
+ // src/ownership/resume.ts
1976
+ import { Unowned as Unowned5 } from "alchemy/AdoptPolicy";
1977
+ import { Artifacts } from "alchemy/Artifacts";
1978
+ import * as Effect24 from "effect/Effect";
1979
+ import * as Option3 from "effect/Option";
1980
+ var PROVEN = "homeflare/ownership/resumes";
1981
+ var UNFINISHED = "homeflare/ownership/unfinished";
1982
+ var note = (key, instanceId) => Effect24.gen(function* () {
1983
+ const bag = yield* Effect24.serviceOption(Artifacts);
1984
+ if (Option3.isSome(bag))
1985
+ yield* bag.value.set(key, instanceId);
1986
+ });
1987
+ var noted = (key, instanceId) => Effect24.gen(function* () {
1988
+ const bag = yield* Effect24.serviceOption(Artifacts);
1989
+ if (Option3.isNone(bag))
1990
+ return false;
1991
+ return (yield* bag.value.get(key)) === instanceId;
1992
+ });
1993
+ var noteUnfinished = (instanceId) => Effect24.as(note(UNFINISHED, instanceId), undefined);
1994
+ var noteResume = (instanceId, recovered) => Effect24.gen(function* () {
1995
+ yield* noteUnfinished(instanceId);
1996
+ const unproven = () => Effect24.succeed(undefined);
1997
+ const found = yield* recovered.pipe(Effect24.catch(unproven), Effect24.catchDefect(unproven));
1998
+ if (found === undefined || found === null || Unowned5.is(found))
1999
+ return;
2000
+ yield* note(PROVEN, instanceId);
2001
+ });
2002
+ var resumes = (instanceId) => noted(PROVEN, instanceId);
2003
+ var unfinished = (instanceId) => noted(UNFINISHED, instanceId);
2004
+ var provingResumes = (service) => {
2005
+ const { diff, read } = service;
2006
+ if (diff === undefined || read === undefined)
2007
+ return service;
2008
+ const proving = (input) => Effect24.gen(function* () {
2009
+ if (input.output === undefined) {
2010
+ yield* noteResume(input.instanceId, read({ ...input, output: undefined }));
2011
+ }
2012
+ return yield* diff(input);
2013
+ });
2014
+ return { ...service, diff: proving };
2015
+ };
2016
+
2017
+ // src/ownership/adopt.ts
2018
+ import { AdoptPolicy } from "alchemy/AdoptPolicy";
2019
+ import { AlchemyContext } from "alchemy/AlchemyContext";
2020
+ import { Stack as Stack3 } from "alchemy/Stack";
2021
+ import * as Effect25 from "effect/Effect";
2022
+ import * as Option4 from "effect/Option";
2023
+ var adoptEnabled = (fqn) => Effect25.gen(function* () {
2024
+ const stack = yield* Effect25.serviceOption(Stack3);
2025
+ const scoped = Option4.isSome(stack) ? stack.value.resources[fqn]?.Adopt : undefined;
2026
+ if (scoped !== undefined)
2027
+ return scoped;
2028
+ const policy = yield* Effect25.serviceOption(AdoptPolicy);
2029
+ if (Option4.isSome(policy))
2030
+ return policy.value;
2031
+ const context = yield* Effect25.serviceOption(AlchemyContext);
2032
+ return Option4.isSome(context) ? context.value.adopt : false;
2033
+ });
2034
+ var adoptable = (owner) => Effect25.gen(function* () {
2035
+ if (yield* isCreate(owner.fqn, owner.instanceId))
2036
+ return true;
2037
+ return yield* unfinished(owner.instanceId);
2038
+ });
2039
+ var adoptsAtApply = (owner) => Effect25.gen(function* () {
2040
+ if (!(yield* adoptEnabled(owner.fqn)))
2041
+ return false;
2042
+ return yield* adoptable(owner);
2043
+ });
2044
+ var refuseTakeover = (owner, what) => Effect25.gen(function* () {
2045
+ if (owner.output !== undefined)
2046
+ return;
2047
+ if (yield* resumes(owner.instanceId))
2048
+ return;
2049
+ const may = yield* adoptable(owner);
2050
+ if (may && (yield* adoptEnabled(owner.fqn)))
2051
+ return;
2052
+ yield* forgetRefusedCreate(owner.fqn, owner.instanceId);
2053
+ const remedy = may ? "Deploy with --adopt (or wrap the resource in adopt(true)) to take it over, or remove it first." : "It is the new identity of a replace, which --adopt does not cover: remove it first.";
2054
+ return yield* Effect25.die(new Error(`${what}: already exists, and this stack holds no state for it — the plan could not ask ` + "first (a prop was still an Output, a replace moved onto it, or it appeared after the " + `plan). Nothing was written. ${remedy}`));
2055
+ });
2056
+ var claimFor = (owner) => (what) => refuseTakeover(owner, what);
2057
+
2058
+ // src/postgres/role-diff.ts
2059
+ import { isResolved as isResolved4 } from "alchemy/Diff";
2060
+ import * as Effect26 from "effect/Effect";
2061
+ var diffPostgresRole = (news, output, env = process.env, observation) => Effect26.gen(function* () {
2062
+ if (output === undefined || !isResolved4(news))
2063
+ return;
2064
+ if (news.name !== output.name) {
2065
+ return yield* Effect26.fail(new PostgresRoleRenameRefused({ from: output.name, to: news.name }));
2066
+ }
2067
+ yield* refuseAtPlan(news);
2068
+ const live = observation?.found;
2069
+ if (live !== undefined && live.oid !== output.oid) {
2070
+ return yield* Effect26.fail(new PostgresRoleIdentityRefused({
2071
+ role: output.name,
2072
+ storedOid: output.oid,
2073
+ liveOid: live.oid
2074
+ }));
2075
+ }
2076
+ const compared = live ?? output;
2077
+ const scalars = scalarDrift(news, compared);
2078
+ const membership = membershipDrift(news.memberOf, compared.memberOf ?? [], live === undefined ? new Set : unsafeMemberships(live));
2079
+ const resolved = resolvePassword(news, env);
2080
+ const passwordStale = resolved.value !== undefined && !passwordMatchesSeal(resolved.value, output.passwordSeal);
2081
+ const absent2 = observation !== undefined && live === undefined;
2082
+ const changed = absent2 || scalars.length > 0 || membership.grants.length > 0 || membership.revokes.length > 0 || passwordStale;
2083
+ return changed ? { action: "update" } : { action: "noop" };
2084
+ });
2085
+
2086
+ // src/postgres/role-provider.ts
2087
+ var postgresRoleHandlers = PostgresRole.Provider.of({
2088
+ list: () => Effect27.succeed([]),
2089
+ nuke: { skip: true },
2090
+ read: ({ fqn, instanceId, olds, output }) => Effect27.gen(function* () {
2091
+ const name = output?.name ?? olds.name;
2092
+ const live = yield* withPg((pg) => readRole(pg, name));
2093
+ if (live === undefined)
2094
+ return;
2095
+ if (output !== undefined && live.oid !== output.oid) {
2096
+ return yield* Effect27.fail(new PostgresRoleIdentityRefused({
2097
+ role: name,
2098
+ storedOid: output.oid,
2099
+ liveOid: live.oid
2100
+ }));
2101
+ }
2102
+ const passwordSeal = output?.passwordSeal ?? "";
2103
+ const found = { ...live, passwordSeal };
2104
+ return yield* ownedRead({ fqn, instanceId, output }, found, Effect27.succeed(declarationMatches(olds, live)));
2105
+ }),
2106
+ diff: ({ news, output, instanceId }) => Effect27.gen(function* () {
2107
+ if (output === undefined)
2108
+ return yield* noteUnfinished(instanceId);
2109
+ if (!isResolved5(news))
2110
+ return;
2111
+ const found = yield* withPg((pg) => readRole(pg, output.name));
2112
+ return yield* diffPostgresRole(news, output, process.env, { found });
2113
+ }),
2114
+ reconcile: ({ fqn, instanceId, news, output }) => Effect27.gen(function* () {
2115
+ yield* refuseAtPlan(news);
2116
+ return yield* withPg((pg) => Effect27.gen(function* () {
2117
+ const observed = yield* readRole(pg, news.name);
2118
+ if (observed !== undefined && output === undefined) {
2119
+ yield* refuseTakeover({ fqn, instanceId, output }, "Postgres.Role");
2120
+ }
2121
+ return yield* reconcileWithClient4(pg, news, process.env, output?.passwordSeal, output?.oid);
2122
+ }));
2123
+ }),
2124
+ delete: ({ output }) => withPg((pg) => Effect27.gen(function* () {
2125
+ if (output === undefined)
2126
+ return;
2127
+ const live = yield* readRole(pg, output.name);
2128
+ if (live === undefined)
2129
+ return;
2130
+ if (live.oid !== output.oid) {
2131
+ return yield* Effect27.fail(new PostgresRoleIdentityRefused({
2132
+ role: output.name,
2133
+ storedOid: output.oid,
2134
+ liveOid: live.oid
2135
+ }));
2136
+ }
2137
+ yield* pg.unsafe(buildDropRoleSql(output.name)).pipe(Effect27.asVoid);
2138
+ }))
2139
+ });
2140
+ var PostgresRoleProvider = () => Provider4.succeed(PostgresRole, postgresRoleHandlers);
209
2141
  // src/postgres/providers.ts
210
2142
  import * as Layer2 from "effect/Layer";
211
- var postgresProviders = (config) => PostgresDatabaseProvider().pipe(Layer2.provide(postgresConnection(config)));
2143
+ var postgresProviders = (config) => Layer2.mergeAll(PostgresDatabaseProvider().pipe(Layer2.provideMerge(postgresConnection(config))), PostgresSchemaProvider().pipe(Layer2.provideMerge(postgresConnection(config))), PostgresGrantsProvider().pipe(Layer2.provideMerge(postgresConnection(config))));
2144
+ var postgresRunnerProviders = (config) => Layer2.mergeAll(PostgresDatabaseProvider().pipe(Layer2.provideMerge(postgresRunnerConnection(config))), PostgresSchemaProvider().pipe(Layer2.provideMerge(postgresRunnerConnection(config))), PostgresGrantsProvider().pipe(Layer2.provideMerge(postgresRunnerConnection(config))));
2145
+ var postgresRoleProviders = (config) => PostgresRoleProvider().pipe(Layer2.provideMerge(postgresConnection(config)));
2146
+ var postgresRoleRunnerProviders = (config) => PostgresRoleProvider().pipe(Layer2.provideMerge(postgresRunnerConnection(config)));
212
2147
  export {
2148
+ COLUMN_PRIVILEGES,
213
2149
  POSTGRES_NAME_MAX_BYTES,
214
2150
  PostgresConnection,
215
2151
  PostgresDatabase,
@@ -219,20 +2155,108 @@ export {
219
2155
  PostgresDatabaseOwnerMissing,
220
2156
  PostgresDatabaseProvider,
221
2157
  PostgresDatabaseRenameRefused,
2158
+ PostgresGrants,
2159
+ PostgresGrantsColumnMissing,
2160
+ PostgresGrantsDatabaseMismatch,
2161
+ PostgresGrantsDatabaseMissing,
2162
+ PostgresGrantsDuplicateObject,
2163
+ PostgresGrantsNameRefused,
2164
+ PostgresGrantsPrivilegeRefused,
2165
+ PostgresGrantsProvider,
2166
+ PostgresGrantsRepairRefused,
2167
+ PostgresGrantsRetargetRefused,
2168
+ PostgresGrantsRoleMissing,
2169
+ PostgresGrantsSchemaMissing,
2170
+ PostgresGrantsTableMissing,
2171
+ PostgresRole,
2172
+ PostgresRoleCreateVanished,
2173
+ PostgresRoleIdentityRefused,
2174
+ PostgresRoleMembershipUnrepaired,
2175
+ PostgresRoleNameRefused,
2176
+ PostgresRoleParentMissing,
2177
+ PostgresRolePasswordEnvUnsetError,
2178
+ PostgresRolePrivilegedRefused,
2179
+ PostgresRoleProvider,
2180
+ PostgresRoleRenameRefused,
2181
+ PostgresRoleValidUntilRefused,
2182
+ PostgresSchema,
2183
+ PostgresSchemaCreateVanished,
2184
+ PostgresSchemaDatabaseRefused,
2185
+ PostgresSchemaDeleteForeignRefused,
2186
+ PostgresSchemaDrift,
2187
+ PostgresSchemaDropNotEmptyError,
2188
+ PostgresSchemaExistsRefused,
2189
+ PostgresSchemaIdentityRefused,
2190
+ PostgresSchemaNameRefused,
2191
+ PostgresSchemaOwnerMissing,
2192
+ PostgresSchemaProvider,
2193
+ PostgresSchemaRenameRefused,
2194
+ PostgresSchemaWrongDatabase,
2195
+ SCHEMA_PRIVILEGES,
2196
+ TABLE_PRIVILEGES,
2197
+ buildAlterRoleSql,
2198
+ buildCommentSchemaSql,
222
2199
  buildCreateDatabaseSql,
2200
+ buildCreateRoleSql,
2201
+ buildCreateSchemaSql,
2202
+ buildDropRoleSql,
2203
+ buildDropSchemaSql,
2204
+ buildGrantMembershipSql,
2205
+ buildRepairMembershipSql,
2206
+ buildRevokeGrantorMembershipSql,
2207
+ buildSetPasswordSql,
2208
+ currentDatabase,
2209
+ currentUser,
2210
+ databaseExists,
2211
+ diffPostgresSchema,
223
2212
  firstDrift,
2213
+ grantColumnSql,
2214
+ grantDefaultSql,
2215
+ grantSchemaSql,
2216
+ grantTableSql,
2217
+ grantsNameByteRefusal,
2218
+ inlineParams,
2219
+ isDependentObjectsError,
224
2220
  isDuplicateDatabaseRace,
225
2221
  isPostgresDatabase,
2222
+ isPostgresGrants,
2223
+ isPostgresRole,
2224
+ isPostgresSchema,
2225
+ letterForPrivilege,
2226
+ makePsqlExecutor,
2227
+ membershipDrift,
226
2228
  nameByteRefusal,
2229
+ normalizedComment,
227
2230
  postgresConnection,
228
2231
  postgresProviders,
2232
+ postgresRoleProviders,
2233
+ postgresRoleRunnerProviders,
2234
+ postgresRunnerConnection,
2235
+ postgresRunnerProviders,
229
2236
  quoteIdent,
230
2237
  quoteStringLiteral,
2238
+ resolvePassword,
2239
+ revokeColumnSql,
2240
+ revokeDefaultSql,
2241
+ revokePublicSchemaSql,
2242
+ revokePublicTablesSql,
2243
+ revokeSchemaSql,
2244
+ revokeTableSql,
231
2245
  roleExists,
2246
+ sameValidUntil,
2247
+ scalarDrift,
2248
+ schemaIsEmpty,
2249
+ schemaNameByteRefusal,
232
2250
  selectDatabase,
2251
+ selectRole,
2252
+ selectRoleMemberships,
2253
+ selectSchema,
2254
+ syncMemberships,
2255
+ unsafeMemberships,
233
2256
  utf8ByteLength,
2257
+ validUntilRefusal,
234
2258
  withPg
235
2259
  };
236
2260
 
237
- //# debugId=84B82CBA6D8F3F5F64756E2164756E21
2261
+ //# debugId=C4337C797D2364A164756E2164756E21
238
2262
  //# sourceMappingURL=index.js.map