@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
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Every plan-time refusal for `Postgres.Schema`, checked without a client: name-byte
3
+ * precision, identifier and literal quoting, the rename and database-move refusals, and the
4
+ * sweep proving `diff` never answers `replace` — including the `cascade` flip compared against
5
+ * `olds` so the change reaches the persisted props. The real handlers over the runner transport
6
+ * live in `schema-handlers.test.ts`.
7
+ */
8
+ import { describe, expect, test } from 'bun:test';
9
+ import * as Effect from 'effect/Effect';
10
+ import { POSTGRES_NAME_MAX_BYTES, utf8ByteLength } from './database-attrs.ts';
11
+ import { quoteIdent, quoteStringLiteral } from './database-sql.ts';
12
+ import { buildCommentSchemaSql, buildCreateSchemaSql } from './schema-sql.ts';
13
+ import { schemaNameByteRefusal } from './schema-attrs.ts';
14
+ import type { PostgresSchemaAttributes, PostgresSchemaProps } from './schema-attrs.ts';
15
+ import { diffPostgresSchema } from './schema-diff.ts';
16
+ import {
17
+ PostgresSchemaDatabaseRefused,
18
+ PostgresSchemaNameRefused,
19
+ PostgresSchemaRenameRefused,
20
+ } from './schema-errors.ts';
21
+
22
+ describe('name byte length', () => {
23
+ test('a 63-byte ASCII name is accepted', () => {
24
+ const name = 'a'.repeat(63);
25
+ expect(utf8ByteLength(name)).toBe(63);
26
+ expect(schemaNameByteRefusal(name)).toBeUndefined();
27
+ });
28
+
29
+ test('a 64-byte ASCII name is refused', () => {
30
+ const name = 'a'.repeat(64);
31
+ const refusal = schemaNameByteRefusal(name);
32
+ expect(refusal).toEqual({ byteLength: 64, limit: POSTGRES_NAME_MAX_BYTES });
33
+ });
34
+
35
+ test('a 63-byte multibyte name (21 three-byte characters) is accepted, though .length is 21', () => {
36
+ // U+4E2D ("中") is 3 UTF-8 bytes. 21 * 3 = 63 bytes exactly — the last size that survives.
37
+ const name = '中'.repeat(21);
38
+ expect(name.length).toBe(21);
39
+ expect(utf8ByteLength(name)).toBe(63);
40
+ expect(schemaNameByteRefusal(name)).toBeUndefined();
41
+ });
42
+
43
+ test('one more multibyte character (64 bytes) is refused', () => {
44
+ const name = `${'中'.repeat(21)}a`;
45
+ expect(utf8ByteLength(name)).toBe(64);
46
+ expect(schemaNameByteRefusal(name)).toEqual({ byteLength: 64, limit: POSTGRES_NAME_MAX_BYTES });
47
+ });
48
+
49
+ test('the typed refusal names the truncation danger in its message', () => {
50
+ expect(
51
+ new PostgresSchemaNameRefused({ name: 'x', byteLength: 64, limit: 63 }).message,
52
+ ).toContain('silently truncate');
53
+ });
54
+ });
55
+
56
+ describe('quoting', () => {
57
+ test('quoteIdent doubles an embedded double quote and leaves a hyphen alone', () => {
58
+ expect(quoteIdent('a"b-c')).toBe('"a""b-c"');
59
+ });
60
+
61
+ test('quoteStringLiteral doubles an embedded single quote', () => {
62
+ expect(quoteStringLiteral("seat ledger'; DROP")).toBe("E'seat ledger''; DROP'");
63
+ });
64
+
65
+ test('buildCreateSchemaSql never string-concatenates an unescaped value', () => {
66
+ const sql = buildCreateSchemaSql({ name: 'sc"1', database: 'postgres', owner: 'ro"le' });
67
+ expect(sql).toBe('CREATE SCHEMA "sc""1" AUTHORIZATION "ro""le"');
68
+ });
69
+
70
+ test('buildCommentSchemaSql never string-concatenates an unescaped comment', () => {
71
+ const sql = buildCommentSchemaSql('sc"1', "it'; DROP");
72
+ expect(sql).toBe(`COMMENT ON SCHEMA "sc""1" IS E'it''; DROP'`);
73
+ });
74
+ });
75
+
76
+ const sampleOutput: PostgresSchemaAttributes = {
77
+ name: 'ledger',
78
+ database: 'postgres',
79
+ oid: 1,
80
+ owner: 'tim',
81
+ comment: null,
82
+ };
83
+ const base: PostgresSchemaProps = { name: 'ledger', database: 'postgres', owner: 'tim' };
84
+
85
+ describe('diff', () => {
86
+ test('a rename is refused at plan, before reconcile ever runs', async () => {
87
+ const error = await Effect.runPromise(
88
+ Effect.flip(diffPostgresSchema({ ...base, name: 'gadgets' }, sampleOutput, base)),
89
+ );
90
+ expect(error).toBeInstanceOf(PostgresSchemaRenameRefused);
91
+ });
92
+
93
+ test('a database move is refused at plan, the same way a rename is', async () => {
94
+ const error = await Effect.runPromise(
95
+ Effect.flip(diffPostgresSchema({ ...base, database: 'agents' }, sampleOutput, base)),
96
+ );
97
+ expect(error).toBeInstanceOf(PostgresSchemaDatabaseRefused);
98
+ });
99
+
100
+ test('an unchanged declaration answers noop', async () => {
101
+ const result = await Effect.runPromise(diffPostgresSchema(base, sampleOutput, base));
102
+ expect(result).toEqual({ action: 'noop' });
103
+ });
104
+
105
+ test('an unchanged cascade answers noop even when the previous declaration carried it', async () => {
106
+ const result = await Effect.runPromise(
107
+ diffPostgresSchema({ ...base, cascade: true }, sampleOutput, { ...base, cascade: true }),
108
+ );
109
+ expect(result).toEqual({ action: 'noop' });
110
+ });
111
+
112
+ test('a change in any single prop answers update, and NEVER replace — swept across every prop', async () => {
113
+ const variants: ReadonlyArray<Partial<PostgresSchemaProps>> = [
114
+ { owner: 'someone-else' },
115
+ { comment: 'changed' },
116
+ { cascade: true },
117
+ ];
118
+ for (const variant of variants) {
119
+ const result = await Effect.runPromise(
120
+ diffPostgresSchema({ ...base, ...variant }, sampleOutput, base),
121
+ );
122
+ expect(result?.action, `variant ${JSON.stringify(variant)}`).toBe('update');
123
+ expect(result).not.toEqual({ action: 'replace' });
124
+ }
125
+ });
126
+
127
+ test('a cascade flip answers update in BOTH directions — compared against olds, so the flip reaches the persisted props', async () => {
128
+ // The engine's noop branch commits the old props (Apply.ts), so a cascade flip that
129
+ // answered noop would leave the persisted props carrying the OLD value forever: a schema
130
+ // first declared cascade: true would keep dropping with CASCADE even after the
131
+ // declaration said false.
132
+ const down = await Effect.runPromise(
133
+ diffPostgresSchema(base, sampleOutput, { ...base, cascade: true }),
134
+ );
135
+ expect(down?.action).toBe('update');
136
+ });
137
+
138
+ test('a declared empty comment diffs as "no comment", never as a change against a NULL', async () => {
139
+ const result = await Effect.runPromise(
140
+ diffPostgresSchema({ ...base, comment: '' }, sampleOutput, base),
141
+ );
142
+ expect(result).toEqual({ action: 'noop' });
143
+ });
144
+ });
@@ -0,0 +1,161 @@
1
+ /**
2
+ * The `Postgres.Schema` socket transport, mocked at the pool: what the review measured was a
3
+ * cold plan's `read` opening the declared database directly and failing untyped
4
+ * (`UnknownError`, SQLSTATE `3D000` — the pinned driver classification below). The handler
5
+ * now probes `pg_database` over the FAMILY pool first and treats a missing declared database
6
+ * as schema-absence, and the operation's pool opens the declared database through the
7
+ * `withPg` override — the config each `PgClient.layer` call receives is recorded here.
8
+ */
9
+ import { beforeEach, describe, expect, test } from 'bun:test';
10
+ import * as PgClientModule from '@effect/sql-pg/PgClient';
11
+ import type { PgClient as PgClientService } from '@effect/sql-pg/PgClient';
12
+ import * as Effect from 'effect/Effect';
13
+ import * as Layer from 'effect/Layer';
14
+ import { SqlClient } from 'effect/unstable/sql/SqlClient';
15
+ import { isDependentObjectsError } from './schema-sql.ts';
16
+ import { classifyInstalled } from './installed-classifier.ts';
17
+ import { PostgresPool, postgresConnection } from './connection.ts';
18
+ import { postgresSchemaHandlers } from './schema.ts';
19
+ import { PostgresSchemaDeleteForeignRefused } from './schema-errors.ts';
20
+ import type { PostgresSchemaAttributes, PostgresSchemaProps } from './schema-attrs.ts';
21
+ import { type RouteAnswers, deleteArgs, readArgs, route } from './schema-test-kit.ts';
22
+ import { Unowned } from 'alchemy/AdoptPolicy';
23
+
24
+ if (postgresSchemaHandlers.read === undefined) {
25
+ throw new Error('Postgres.Schema is missing its read handler');
26
+ }
27
+ /** Narrowed once at module scope — the narrowing does not survive into `test` callbacks. */
28
+ const schemaRead = postgresSchemaHandlers.read;
29
+
30
+ /** Every `PgClient.layer` config the mocked layer was built with — one per `withPg` call. */
31
+ const pools: Array<{ database?: string | undefined }> = [];
32
+ /** Every SQL text the fake client ran, in order. */
33
+ const queries: string[] = [];
34
+ /** The router answers for the current test (`schema-test-kit.ts` markers, JSON strings). */
35
+ let answers: RouteAnswers = {};
36
+
37
+ // The fake service: `unsafe` is the only member the schema handlers call (`withPg`'s build
38
+ // callbacks), and `Statement<A>` is an `Effect<ReadonlyArray<A>, SqlError>` — a plain succeed
39
+ // satisfies the same call shape.
40
+ const fakeClient = {
41
+ unsafe: (text: string) => {
42
+ queries.push(text);
43
+ const answer = route(text, answers);
44
+ return Effect.succeed(
45
+ (answer === undefined ? [] : (JSON.parse(answer) as ReadonlyArray<object>)) as never,
46
+ );
47
+ },
48
+ } as unknown as PgClientService;
49
+
50
+ // The pool factory is provided to this Effect only; other suites keep the real driver.
51
+ const socket = Layer.merge(
52
+ postgresConnection({ host: '/tmp', database: 'postgres', username: 'postgres' }),
53
+ Layer.succeed(PostgresPool, (config) => {
54
+ pools.push(config);
55
+ return Layer.merge(
56
+ Layer.succeed(PgClientModule.PgClient, fakeClient),
57
+ Layer.succeed(SqlClient, fakeClient),
58
+ );
59
+ }),
60
+ );
61
+ const declared: PostgresSchemaProps = { name: 's4', database: 'agents', owner: 'seat_role' };
62
+ const persisted: PostgresSchemaAttributes = {
63
+ name: 's4',
64
+ database: 'agents',
65
+ oid: 16442,
66
+ owner: 'seat_role',
67
+ comment: null,
68
+ };
69
+
70
+ describe('socket transport: a missing declared database is schema absent', () => {
71
+ beforeEach(() => {
72
+ pools.length = 0;
73
+ queries.length = 0;
74
+ answers = {};
75
+ });
76
+
77
+ test('delete is idempotent success: one probe over the FAMILY pool, no connection to the declared database', async () => {
78
+ answers = { probe: '[]' };
79
+ await Effect.runPromise(
80
+ postgresSchemaHandlers
81
+ .delete(deleteArgs({ ...declared, cascade: true }, persisted))
82
+ .pipe(Effect.provide(socket)),
83
+ );
84
+ expect(pools.length).toBe(1);
85
+ expect(pools[0]?.database).toBe('postgres');
86
+ expect(queries.length).toBe(1);
87
+ expect(queries[0]).toContain('FROM pg_database');
88
+ expect(queries.some((q) => q.startsWith('DROP SCHEMA'))).toBe(false);
89
+ });
90
+
91
+ test('read answers absent: the untyped 3D000 connect never happens', async () => {
92
+ answers = { probe: '[]' };
93
+ const result = await Effect.runPromise(
94
+ schemaRead(readArgs(declared)).pipe(Effect.provide(socket)),
95
+ );
96
+ expect(result).toBeUndefined();
97
+ expect(pools.length).toBe(1);
98
+ expect(pools[0]?.database).toBe('postgres');
99
+ expect(queries.length).toBe(1);
100
+ expect(queries[0]).toContain('FROM pg_database');
101
+ });
102
+
103
+ test('the `database` override reaches the pool: probe over the family pool, statements over the declared one', async () => {
104
+ answers = {
105
+ probe: '[{"present":1}]',
106
+ schema: '[{"name":"s4","oid":16442,"owner":"seat_role","comment":null,"database":"agents"}]',
107
+ empty: '[{"empty":true}]',
108
+ proof: '[{"database":"agents"}]',
109
+ };
110
+ await Effect.runPromise(
111
+ postgresSchemaHandlers.delete(deleteArgs(declared, persisted)).pipe(Effect.provide(socket)),
112
+ );
113
+ // The probe opened the family database; every statement after it opened `agents`.
114
+ expect(pools.map((pool) => pool.database)).toEqual(['postgres', 'agents']);
115
+ expect(queries.some((q) => q.startsWith('DROP SCHEMA'))).toBe(true);
116
+ });
117
+
118
+ test('read over an existing declared database answers the row from the overridden pool', async () => {
119
+ answers = {
120
+ probe: '[{"present":1}]',
121
+ schema: '[{"name":"s4","oid":16442,"owner":"seat_role","comment":null,"database":"agents"}]',
122
+ proof: '[{"database":"agents"}]',
123
+ };
124
+ const result = await Effect.runPromise(
125
+ schemaRead(readArgs(declared)).pipe(Effect.provide(socket)),
126
+ );
127
+ expect(pools.map((pool) => pool.database)).toEqual(['postgres', 'agents']);
128
+ expect(Unowned.is(result)).toBe(true);
129
+ expect({ ...(result as object) }).toEqual({ ...persisted });
130
+ });
131
+
132
+ test('delete still proves ownership over the socket: an out-of-band recreation is refused', async () => {
133
+ // The re-read row is another role's (the review's s4 scenario): the persisted proof cannot
134
+ // vouch for it, and the socket pool never issues the DROP.
135
+ answers = {
136
+ probe: '[{"present":1}]',
137
+ schema: '[{"name":"s4","oid":16443,"owner":"other_role","comment":null,"database":"agents"}]',
138
+ proof: '[{"database":"agents"}]',
139
+ };
140
+ const error = await Effect.runPromise(
141
+ Effect.flip(
142
+ postgresSchemaHandlers
143
+ .delete(deleteArgs({ ...declared, cascade: true }, persisted))
144
+ .pipe(Effect.provide(socket)),
145
+ ),
146
+ );
147
+ expect(error).toBeInstanceOf(PostgresSchemaDeleteForeignRefused);
148
+ expect(error).toMatchObject({ liveOid: 16443, liveOwner: 'other_role', lastOid: 16442 });
149
+ expect(queries.some((q) => q.startsWith('DROP SCHEMA'))).toBe(false);
150
+ });
151
+
152
+ test('the installed classifier keeps 3D000 as UnknownError with the raw code — the untyped failure the probe prevents', async () => {
153
+ // `classifySqlState` only types class `42` as `SqlSyntaxError`; `3D000` (`invalid_catalog_name`)
154
+ // is class `3D`, so a socket connect to a missing database surfaces as this shape today —
155
+ // the reason `read`/`delete` probe `pg_database` first instead of opening it.
156
+ const error = await classifyInstalled('3D000', 'database "agents" does not exist');
157
+ expect(error.reason._tag).toBe('UnknownError');
158
+ expect((error.reason as { cause?: { code?: string } }).cause?.code).toBe('3D000');
159
+ expect(isDependentObjectsError(error)).toBe(false);
160
+ });
161
+ });
@@ -0,0 +1,130 @@
1
+ /**
2
+ * The statements `Postgres.Schema` issues and the quoting each needs.
3
+ *
4
+ * ⛔ `CREATE SCHEMA` TAKES NO BIND PARAMETERS. Measured at `gram.y@REL_18_6`:
5
+ * `CreateSchemaStmt: CREATE SCHEMA [IF NOT EXISTS] opt_schema_name AUTHORIZATION
6
+ * RoleSpec | create_schema_name opt_schema_element_list`. Neither the name nor the
7
+ * authorization role reaches `PARAM` (`$n`), so every free-form value is escaped by hand
8
+ * (`quoteIdent`) and the assembled text runs through `.unsafe()`.
9
+ * ★ The schema name is a single `ColId` token, so `quoteIdent` (single-token quoting) is
10
+ * correct — same rule as `Postgres.Database`'s name.
11
+ */
12
+ import type { SqlError } from 'effect/unstable/sql/SqlError';
13
+ import * as Effect from 'effect/Effect';
14
+ import type { PostgresSchemaAttributes, PostgresSchemaProps } from './schema-attrs.ts';
15
+ import { quoteIdent, quoteStringLiteral } from './database-sql.ts';
16
+ import type { PgExecutor } from './database-sql.ts';
17
+
18
+ /** `CREATE SCHEMA "name" [AUTHORIZATION "owner"]` — the only create this family issues. */
19
+ export const buildCreateSchemaSql = (props: PostgresSchemaProps): string =>
20
+ `CREATE SCHEMA ${quoteIdent(props.name)}${
21
+ props.owner === undefined ? '' : ` AUTHORIZATION ${quoteIdent(props.owner)}`
22
+ }`;
23
+
24
+ /** `COMMENT ON SCHEMA "name" IS E'…'` — issued only when a comment is declared. */
25
+ export const buildCommentSchemaSql = (name: string, comment: string): string =>
26
+ `COMMENT ON SCHEMA ${quoteIdent(name)} IS ${quoteStringLiteral(comment)}`;
27
+
28
+ /** `DROP SCHEMA IF EXISTS "name" [CASCADE]` — idempotent; `CASCADE` only when declared. */
29
+ export const buildDropSchemaSql = (name: string, cascade: boolean): string =>
30
+ `DROP SCHEMA IF EXISTS ${quoteIdent(name)}${cascade ? ' CASCADE' : ''}`;
31
+
32
+ const SELECT_SCHEMA_SQL = `SELECT
33
+ n.oid AS oid,
34
+ n.nspname AS name,
35
+ pg_get_userbyid(n.nspowner) AS owner,
36
+ obj_description(n.oid, 'pg_namespace') AS comment,
37
+ current_database() AS database
38
+ FROM pg_namespace n
39
+ WHERE n.nspname = $1`;
40
+
41
+ /** The read: one bound `SELECT` on `pg_namespace`. `undefined` when absent. The row carries
42
+ * `current_database()` as `database`, so every caller can prove where the statement ran. */
43
+ export const selectSchema = (
44
+ pg: PgExecutor,
45
+ name: string,
46
+ ): Effect.Effect<PostgresSchemaAttributes | undefined, SqlError> =>
47
+ Effect.map(pg.unsafe<PostgresSchemaAttributes>(SELECT_SCHEMA_SQL, [name]), (rows) => rows[0]);
48
+
49
+ const CURRENT_DATABASE_SQL = 'SELECT current_database() AS database';
50
+
51
+ /** Which database this executor actually reaches — checked before any `CREATE` or `DROP SCHEMA`,
52
+ * so a schema declared for `agents` can never quietly land in the maintenance database the
53
+ * family connection points at. */
54
+ export const currentDatabase = (pg: PgExecutor): Effect.Effect<string, SqlError> =>
55
+ Effect.map(
56
+ pg.unsafe<{ readonly database: string }>(CURRENT_DATABASE_SQL),
57
+ // current_database() always answers one row; an empty fallback can only compare unequal,
58
+ // failing closed into PostgresSchemaWrongDatabase.
59
+ (rows) => rows[0]?.database ?? '',
60
+ );
61
+
62
+ /**
63
+ * The role a `CREATE SCHEMA` without `AUTHORIZATION` is owned by. Measured at
64
+ * `gram.y@REL_18_6`: absent `AUTHORIZATION`, the schema owner is the current user.
65
+ * `reconcile` compares an omitted `owner` to this, so a race winner owned by someone else
66
+ * is drift rather than an adopted row.
67
+ */
68
+ const CURRENT_USER_SQL = 'SELECT current_user AS role';
69
+
70
+ export const currentUser = (pg: PgExecutor): Effect.Effect<string, SqlError> =>
71
+ Effect.map(
72
+ pg.unsafe<{ readonly role: string }>(CURRENT_USER_SQL),
73
+ // An empty answer can only compare unequal, failing closed into PostgresSchemaDrift.
74
+ (rows) => rows[0]?.role ?? '',
75
+ );
76
+
77
+ /**
78
+ * "Empty" means no object of ANY kind the schema owns. Measured object kinds live in four
79
+ * catalogs: relations (tables, views, indexes, sequences — `pg_class`), functions and
80
+ * aggregates (`pg_proc`), types, domains and enums (`pg_type`), operators (`pg_operator`). A
81
+ * kind without a dedicated catalog (an extension's `extnamespace`, a collation) can still pass
82
+ * this check — and then the plain `DROP SCHEMA` answers `2BP01`, which `dropWithClient`
83
+ * classifies as the same typed refusal.
84
+ *
85
+ * ⚠️ THE FAKE (`fake-schema-sql.ts`) ROUTES THIS STATEMENT BY `AS empty` + `pg_class` — keep both
86
+ * markers in any rewrite, and keep this check matched BEFORE the plain `pg_namespace` branch
87
+ * (this SQL contains `FROM pg_namespace` too, inside its `WITH`).
88
+ */
89
+ const SCHEMA_EMPTY_SQL = `WITH ns AS (SELECT oid FROM pg_namespace WHERE nspname = $1)
90
+ SELECT NOT EXISTS (
91
+ SELECT 1 FROM pg_class c WHERE c.relnamespace = (SELECT oid FROM ns)
92
+ ) AND NOT EXISTS (
93
+ SELECT 1 FROM pg_proc p WHERE p.pronamespace = (SELECT oid FROM ns)
94
+ ) AND NOT EXISTS (
95
+ SELECT 1 FROM pg_type t WHERE t.typnamespace = (SELECT oid FROM ns)
96
+ ) AND NOT EXISTS (
97
+ SELECT 1 FROM pg_operator o WHERE o.oprnamespace = (SELECT oid FROM ns)
98
+ ) AS empty`;
99
+
100
+ /** `true` when the schema holds none of the four object catalogs above (used before a
101
+ * non-cascade `DROP SCHEMA`). */
102
+ export const schemaIsEmpty = (pg: PgExecutor, name: string): Effect.Effect<boolean, SqlError> =>
103
+ Effect.map(
104
+ pg.unsafe<{ readonly empty: boolean }>(SCHEMA_EMPTY_SQL, [name]),
105
+ (rows) => rows[0]?.empty ?? true,
106
+ );
107
+
108
+ /** SQLSTATE `2BP01` (`dependent_objects_still_exist`): the non-cascade `DROP SCHEMA` refused
109
+ * because an object kind outside the emptiness check's four catalogs still exists.
110
+ *
111
+ * Measured at `@effect/sql-pg@4.0.0-rc.115` `src/internal/sqlError.ts#classifySqlState`: only
112
+ * class `42` becomes `SqlSyntaxError`. `2BP01` is class `2B`, so it falls through to
113
+ * `UnknownError` (`sqlError.ts:74`). `PgConnection.ts#classifyFields` still puts the raw code
114
+ * on `reason.cause.code`. The runner transport copies that rule (`psql-executor.ts#failure`):
115
+ * `SqlSyntaxError` only when `state.startsWith('42')`, otherwise `UnknownError` with the same
116
+ * `cause.code`. Reading the code, not the tag, is what makes a real server refusal match. */
117
+ export const isDependentObjectsError = (error: SqlError): boolean => {
118
+ const cause = error.reason.cause;
119
+ return (
120
+ typeof cause === 'object' && cause !== null && (cause as { code?: unknown }).code === '2BP01'
121
+ );
122
+ };
123
+
124
+ /** REL_18_6 pg_namespace.c raises duplicate_schema; both transports retain cause.code. */
125
+ export const isDuplicateSchemaError = (error: SqlError): boolean => {
126
+ const cause = error.reason.cause;
127
+ return (
128
+ typeof cause === 'object' && cause !== null && (cause as { code?: unknown }).code === '42P06'
129
+ );
130
+ };
@@ -0,0 +1,149 @@
1
+ /** Regressions for unresolved-Output apply paths, driven through the actual runner handlers. */
2
+ import { expect, test } from 'bun:test';
3
+ import * as Effect from 'effect/Effect';
4
+ import { postgresSchemaHandlers, reconcileWithClient } from './schema.ts';
5
+ import { postgresRunnerConnection } from './connection.ts';
6
+ import { makeFakeSql } from './fake-sql.ts';
7
+ import { readArgs, router, runnerWith } from './schema-test-kit.ts';
8
+ import type { PostgresSchemaAttributes } from './schema-attrs.ts';
9
+
10
+ const props = { name: 'ledger', database: 'postgres', owner: 'postgres', cascade: true };
11
+ const stored: PostgresSchemaAttributes = { ...props, oid: 25533, comment: null };
12
+ const replacement = { ...stored, oid: 25534 };
13
+ const args = (output?: PostgresSchemaAttributes) => ({
14
+ ...readArgs(props),
15
+ news: props,
16
+ output,
17
+ session: undefined as never,
18
+ });
19
+
20
+ for (const name of ['ledger', 'public']) {
21
+ test(`apply refuses existing ${name} without output even when owner matches`, async () => {
22
+ const { run, stdins } = runnerWith(
23
+ router({ proof: '[{"database":"postgres"}]', schema: JSON.stringify([{ ...stored, name }]) }),
24
+ );
25
+ const error = await Effect.runPromise(
26
+ postgresSchemaHandlers
27
+ .reconcile({ ...args(), news: { ...props, name } })
28
+ .pipe(
29
+ Effect.provide(
30
+ postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
31
+ ),
32
+ Effect.flip,
33
+ ),
34
+ );
35
+ expect(error).toMatchObject({ _tag: 'PostgresSchemaExistsRefused' });
36
+ expect(stdins.every((s) => s.startsWith('SELECT'))).toBe(true);
37
+ });
38
+ }
39
+
40
+ test('update refuses a recycled oid before it can replace the delete proof', async () => {
41
+ const fake = makeFakeSql({ schemas: [replacement], schemasWithRelations: ['ledger'] });
42
+ const error = await Effect.runPromise(Effect.flip(reconcileWithClient(fake, props, stored)));
43
+ expect(error).toMatchObject({
44
+ _tag: 'PostgresSchemaIdentityRefused',
45
+ storedOid: 25533,
46
+ liveOid: 25534,
47
+ });
48
+ expect(fake.schemas.get('ledger')).toEqual(replacement);
49
+ expect(fake.relationsIn.has('ledger')).toBe(true);
50
+ });
51
+
52
+ test('read refuses a recycled oid instead of refreshing persisted proof', async () => {
53
+ const { run } = runnerWith(
54
+ router({ probe: '[{"present":1}]', schema: JSON.stringify([replacement]) }),
55
+ );
56
+ const read = postgresSchemaHandlers.read;
57
+ if (read === undefined) throw new Error('missing read');
58
+ const error = await Effect.runPromise(
59
+ read({ ...readArgs(props), output: stored }).pipe(
60
+ Effect.provide(postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' })),
61
+ Effect.flip,
62
+ ),
63
+ );
64
+ expect(error).toMatchObject({ _tag: 'PostgresSchemaIdentityRefused' });
65
+ });
66
+
67
+ for (const [change, tag] of [
68
+ [{ name: 'new_ledger' }, 'PostgresSchemaRenameRefused'],
69
+ [{ database: 'new_database' }, 'PostgresSchemaDatabaseRefused'],
70
+ ] as const) {
71
+ test(`apply repeats ${tag} after unresolved diff`, async () => {
72
+ const { run, stdins } = runnerWith(
73
+ router({
74
+ proof: JSON.stringify([{ database: change.database ?? 'postgres' }]),
75
+ schema: JSON.stringify([{ ...stored, ...change }]),
76
+ }),
77
+ );
78
+ const error = await Effect.runPromise(
79
+ postgresSchemaHandlers
80
+ .reconcile({ ...args(stored), news: { ...props, ...change } })
81
+ .pipe(
82
+ Effect.provide(
83
+ postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
84
+ ),
85
+ Effect.flip,
86
+ ),
87
+ );
88
+ expect(error).toMatchObject({ _tag: tag });
89
+ expect(stdins).toEqual([]);
90
+ });
91
+ }
92
+
93
+ test('same-owner create race is a typed exists refusal, never a comment or adoption', async () => {
94
+ const fake = makeFakeSql({ roles: ['postgres'], raceNextCreateSchema: 'postgres' });
95
+ const error = await Effect.runPromise(
96
+ Effect.flip(reconcileWithClient(fake, { ...props, comment: 'ours' })),
97
+ );
98
+ expect(error).toMatchObject({ _tag: 'PostgresSchemaExistsRefused' });
99
+ expect(fake.schemas.get('ledger')?.comment).toBeNull();
100
+ });
101
+
102
+ test('runner 42P06 becomes an adoption refusal; other SQLSTATEs remain SqlError', async () => {
103
+ for (const code of ['42P06', '42501']) {
104
+ const run = ({ stdin }: { stdin: string }) =>
105
+ Promise.resolve(
106
+ stdin.startsWith('CREATE')
107
+ ? { code: 3, stdout: '', stderr: `ERROR: ${code}: refused` }
108
+ : {
109
+ code: 0,
110
+ stdout:
111
+ stdin.includes('current_database()') && !stdin.includes('pg_namespace')
112
+ ? '[{"database":"postgres"}]'
113
+ : stdin.includes('pg_roles')
114
+ ? '[{"present":1}]'
115
+ : '[]',
116
+ stderr: '',
117
+ },
118
+ );
119
+ const error = await Effect.runPromise(
120
+ postgresSchemaHandlers
121
+ .reconcile(args())
122
+ .pipe(
123
+ Effect.provide(
124
+ postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
125
+ ),
126
+ Effect.flip,
127
+ ),
128
+ );
129
+ expect(error._tag).toBe(code === '42P06' ? 'PostgresSchemaExistsRefused' : 'SqlError');
130
+ }
131
+ });
132
+
133
+ // Apply.ts passes the adopted live attributes as output. The stricter gate must permit that.
134
+ test('explicit adoption output permits a matching schema with zero writes', async () => {
135
+ const { run, stdins } = runnerWith(
136
+ router({ proof: '[{"database":"postgres"}]', schema: JSON.stringify([stored]) }),
137
+ );
138
+ const result = await Effect.runPromise(
139
+ postgresSchemaHandlers
140
+ .reconcile(args(stored))
141
+ .pipe(
142
+ Effect.provide(
143
+ postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
144
+ ),
145
+ ),
146
+ );
147
+ expect(result).toEqual(stored);
148
+ expect(stdins.every((s) => s.startsWith('SELECT'))).toBe(true);
149
+ });
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Shared wiring for the runner-transport `Postgres.Schema` handler tests: the recording
3
+ * `PsqlRunner` (every stdin and its `psql` argv), the handler-argument builders, and the
4
+ * one SQL router they all answer through.
5
+ *
6
+ * ⚠️ THE ROUTER ORDER IS LOAD-BEARING (`schema-sql.ts`): the emptiness check's `WITH`
7
+ * contains `FROM pg_namespace`, and the schema row SELECT contains
8
+ * `current_database() AS database` — so `AS empty` must be matched before
9
+ * `FROM pg_namespace`, and `FROM pg_namespace` before `current_database()`. The database
10
+ * probe (`SELECT 1 AS present FROM pg_database`, `database-sql.ts`) carries no other
11
+ * branch's marker and matches its own.
12
+ */
13
+ import type { PsqlRunner } from './psql-executor.ts';
14
+ import type { PostgresSchemaAttributes, PostgresSchemaProps } from './schema-attrs.ts';
15
+
16
+ export const ok = (stdout: string) => Promise.resolve({ code: 0, stdout, stderr: '' });
17
+
18
+ /** One answer per statement shape a handler issues; `undefined` falls through the router. */
19
+ export interface RouteAnswers {
20
+ /** `schemaIsEmpty` — the pre-DROP emptiness check (`cascade` unset). */
21
+ readonly empty?: string;
22
+ /** `selectSchema` — the schema row re-read, or `[]` for an absent schema. */
23
+ readonly schema?: string;
24
+ /** `databaseExists` — `[{\"present\":1}]` when the declared database exists, `[]` when not. */
25
+ readonly probe?: string;
26
+ /** `current_database()` — the proof that the override reached the declared database. */
27
+ readonly proof?: string;
28
+ }
29
+
30
+ /** Route one stdin to its answer by the markers above, `undefined` when no branch matches. */
31
+ export const route = (stdin: string, answers: RouteAnswers): string | undefined => {
32
+ if (stdin.includes('AS empty')) return answers.empty;
33
+ if (stdin.includes('FROM pg_namespace')) return answers.schema;
34
+ if (stdin.includes('FROM pg_database')) return answers.probe;
35
+ if (stdin.includes('current_database()')) return answers.proof;
36
+ return undefined;
37
+ };
38
+
39
+ /** A `router` that answers every stdin (unrouted statements answer the empty string). */
40
+ export const router = (answers: RouteAnswers) => (stdin: string) => route(stdin, answers) ?? '';
41
+
42
+ /**
43
+ * A recording `PsqlRunner`: every stdin and the `psql` argv are kept, each stdin answered by
44
+ * `answers`. The argv recording pins the `-d` database each statement ran against — the last
45
+ * element of the argv is the `psql -d` value (`psql-executor.ts`).
46
+ */
47
+ export const runnerWith = (
48
+ answers: (stdin: string) => string,
49
+ ): { run: PsqlRunner; stdins: string[]; argvs: Array<readonly string[]> } => {
50
+ const stdins: string[] = [];
51
+ const argvs: Array<readonly string[]> = [];
52
+ return {
53
+ run: ({ stdin, argv }) => {
54
+ stdins.push(stdin);
55
+ argvs.push(argv);
56
+ return Promise.resolve({ code: 0, stdout: answers(stdin), stderr: '' });
57
+ },
58
+ stdins,
59
+ argvs,
60
+ };
61
+ };
62
+
63
+ /** The handler inputs the engine passes on `delete`. `output` is the persisted state — the
64
+ * `oid` + `owner` the last reconcile asserted, which delete must prove the live row still
65
+ * matches before it drops (`schema-delete.ts`). */
66
+ export const deleteArgs = (olds: PostgresSchemaProps, output: PostgresSchemaAttributes) => ({
67
+ id: 'x',
68
+ fqn: 'x',
69
+ instanceId: 'x',
70
+ olds,
71
+ output,
72
+ session: undefined as never,
73
+ bindings: [] as never,
74
+ });
75
+
76
+ /** The handler inputs the engine passes on `read` (undefined output is the adopt path). */
77
+ export const readArgs = (olds: PostgresSchemaProps) => ({
78
+ id: 'x',
79
+ fqn: 'x',
80
+ instanceId: 'x',
81
+ olds,
82
+ output: undefined,
83
+ session: undefined as never,
84
+ bindings: [] as never,
85
+ });