@homeflare/alchemy 0.43.0 → 0.45.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 (612) 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/credential-errors.d.ts +131 -0
  17. package/dist/litellm/credential-errors.d.ts.map +1 -0
  18. package/dist/litellm/credential-form.d.ts +79 -0
  19. package/dist/litellm/credential-form.d.ts.map +1 -0
  20. package/dist/litellm/credential-operations.d.ts +84 -0
  21. package/dist/litellm/credential-operations.d.ts.map +1 -0
  22. package/dist/litellm/credential-rewrite.d.ts +6 -0
  23. package/dist/litellm/credential-rewrite.d.ts.map +1 -0
  24. package/dist/litellm/credential-types.d.ts +87 -0
  25. package/dist/litellm/credential-types.d.ts.map +1 -0
  26. package/dist/litellm/credential-values.d.ts +53 -0
  27. package/dist/litellm/credential-values.d.ts.map +1 -0
  28. package/dist/litellm/credential.d.ts +83 -0
  29. package/dist/litellm/credential.d.ts.map +1 -0
  30. package/dist/litellm/fake-access-group-litellm.d.ts +34 -0
  31. package/dist/litellm/fake-access-group-litellm.d.ts.map +1 -0
  32. package/dist/litellm/fake-credential-litellm.d.ts +54 -0
  33. package/dist/litellm/fake-credential-litellm.d.ts.map +1 -0
  34. package/dist/litellm/fake-keys.d.ts +53 -0
  35. package/dist/litellm/fake-keys.d.ts.map +1 -0
  36. package/dist/litellm/fake-litellm.d.ts +4 -1
  37. package/dist/litellm/fake-litellm.d.ts.map +1 -1
  38. package/dist/litellm/fake-mcp-litellm.d.ts +67 -0
  39. package/dist/litellm/fake-mcp-litellm.d.ts.map +1 -0
  40. package/dist/litellm/fake-model-litellm-post-update.d.ts +49 -0
  41. package/dist/litellm/fake-model-litellm-post-update.d.ts.map +1 -0
  42. package/dist/litellm/fake-model-litellm.d.ts +48 -0
  43. package/dist/litellm/fake-model-litellm.d.ts.map +1 -0
  44. package/dist/litellm/fake-policy-attachment-litellm.d.ts +33 -0
  45. package/dist/litellm/fake-policy-attachment-litellm.d.ts.map +1 -0
  46. package/dist/litellm/fake-policy-litellm.d.ts +32 -0
  47. package/dist/litellm/fake-policy-litellm.d.ts.map +1 -0
  48. package/dist/litellm/fake-registry-base.d.ts +42 -0
  49. package/dist/litellm/fake-registry-base.d.ts.map +1 -0
  50. package/dist/litellm/fake-stack.d.ts +8 -1
  51. package/dist/litellm/fake-stack.d.ts.map +1 -1
  52. package/dist/litellm/fake-team-litellm.d.ts +47 -0
  53. package/dist/litellm/fake-team-litellm.d.ts.map +1 -0
  54. package/dist/litellm/fake-tool-litellm.d.ts +28 -0
  55. package/dist/litellm/fake-tool-litellm.d.ts.map +1 -0
  56. package/dist/litellm/fake-toolset-litellm.d.ts +29 -0
  57. package/dist/litellm/fake-toolset-litellm.d.ts.map +1 -0
  58. package/dist/litellm/index.d.ts +18 -0
  59. package/dist/litellm/index.d.ts.map +1 -1
  60. package/dist/litellm/index.js +3078 -2
  61. package/dist/litellm/index.js.map +65 -4
  62. package/dist/litellm/key-errors.d.ts +169 -0
  63. package/dist/litellm/key-errors.d.ts.map +1 -0
  64. package/dist/litellm/key-form.d.ts +119 -0
  65. package/dist/litellm/key-form.d.ts.map +1 -0
  66. package/dist/litellm/key-harness.d.ts +41 -0
  67. package/dist/litellm/key-harness.d.ts.map +1 -0
  68. package/dist/litellm/key-metadata-errors.d.ts +30 -0
  69. package/dist/litellm/key-metadata-errors.d.ts.map +1 -0
  70. package/dist/litellm/key-metadata.d.ts +22 -0
  71. package/dist/litellm/key-metadata.d.ts.map +1 -0
  72. package/dist/litellm/key-operations.d.ts +63 -0
  73. package/dist/litellm/key-operations.d.ts.map +1 -0
  74. package/dist/litellm/key-secret.d.ts +33 -0
  75. package/dist/litellm/key-secret.d.ts.map +1 -0
  76. package/dist/litellm/key.d.ts +78 -0
  77. package/dist/litellm/key.d.ts.map +1 -0
  78. package/dist/litellm/mcp-server-credential.d.ts +50 -0
  79. package/dist/litellm/mcp-server-credential.d.ts.map +1 -0
  80. package/dist/litellm/mcp-server-errors.d.ts +101 -0
  81. package/dist/litellm/mcp-server-errors.d.ts.map +1 -0
  82. package/dist/litellm/mcp-server-form.d.ts +56 -0
  83. package/dist/litellm/mcp-server-form.d.ts.map +1 -0
  84. package/dist/litellm/mcp-server-locate.d.ts +26 -0
  85. package/dist/litellm/mcp-server-locate.d.ts.map +1 -0
  86. package/dist/litellm/mcp-server-operations.d.ts +63 -0
  87. package/dist/litellm/mcp-server-operations.d.ts.map +1 -0
  88. package/dist/litellm/mcp-server-shadow.d.ts +24 -0
  89. package/dist/litellm/mcp-server-shadow.d.ts.map +1 -0
  90. package/dist/litellm/mcp-server-types.d.ts +118 -0
  91. package/dist/litellm/mcp-server-types.d.ts.map +1 -0
  92. package/dist/litellm/mcp-server-url.d.ts +11 -0
  93. package/dist/litellm/mcp-server-url.d.ts.map +1 -0
  94. package/dist/litellm/mcp-server.d.ts +95 -0
  95. package/dist/litellm/mcp-server.d.ts.map +1 -0
  96. package/dist/litellm/model-claim.d.ts +24 -0
  97. package/dist/litellm/model-claim.d.ts.map +1 -0
  98. package/dist/litellm/model-credential.d.ts +12 -0
  99. package/dist/litellm/model-credential.d.ts.map +1 -0
  100. package/dist/litellm/model-errors.d.ts +119 -0
  101. package/dist/litellm/model-errors.d.ts.map +1 -0
  102. package/dist/litellm/model-form.d.ts +78 -0
  103. package/dist/litellm/model-form.d.ts.map +1 -0
  104. package/dist/litellm/model-locate.d.ts +13 -0
  105. package/dist/litellm/model-locate.d.ts.map +1 -0
  106. package/dist/litellm/model-operations.d.ts +59 -0
  107. package/dist/litellm/model-operations.d.ts.map +1 -0
  108. package/dist/litellm/model-types.d.ts +91 -0
  109. package/dist/litellm/model-types.d.ts.map +1 -0
  110. package/dist/litellm/model.d.ts +88 -0
  111. package/dist/litellm/model.d.ts.map +1 -0
  112. package/dist/litellm/policy-attachment-form.d.ts +19 -0
  113. package/dist/litellm/policy-attachment-form.d.ts.map +1 -0
  114. package/dist/litellm/policy-attachment-operations.d.ts +31 -0
  115. package/dist/litellm/policy-attachment-operations.d.ts.map +1 -0
  116. package/dist/litellm/policy-attachment-types.d.ts +48 -0
  117. package/dist/litellm/policy-attachment-types.d.ts.map +1 -0
  118. package/dist/litellm/policy-attachment.d.ts +61 -0
  119. package/dist/litellm/policy-attachment.d.ts.map +1 -0
  120. package/dist/litellm/policy-form.d.ts +22 -0
  121. package/dist/litellm/policy-form.d.ts.map +1 -0
  122. package/dist/litellm/policy-operations.d.ts +42 -0
  123. package/dist/litellm/policy-operations.d.ts.map +1 -0
  124. package/dist/litellm/policy-types.d.ts +57 -0
  125. package/dist/litellm/policy-types.d.ts.map +1 -0
  126. package/dist/litellm/policy.d.ts +65 -0
  127. package/dist/litellm/policy.d.ts.map +1 -0
  128. package/dist/litellm/providers.d.ts +1 -1
  129. package/dist/litellm/providers.d.ts.map +1 -1
  130. package/dist/litellm/registry-errors.d.ts +62 -0
  131. package/dist/litellm/registry-errors.d.ts.map +1 -0
  132. package/dist/litellm/registry-support.d.ts +42 -0
  133. package/dist/litellm/registry-support.d.ts.map +1 -0
  134. package/dist/litellm/team-form.d.ts +26 -0
  135. package/dist/litellm/team-form.d.ts.map +1 -0
  136. package/dist/litellm/team-members.d.ts +10 -0
  137. package/dist/litellm/team-members.d.ts.map +1 -0
  138. package/dist/litellm/team-operations.d.ts +38 -0
  139. package/dist/litellm/team-operations.d.ts.map +1 -0
  140. package/dist/litellm/team-permission.d.ts +35 -0
  141. package/dist/litellm/team-permission.d.ts.map +1 -0
  142. package/dist/litellm/team-types.d.ts +101 -0
  143. package/dist/litellm/team-types.d.ts.map +1 -0
  144. package/dist/litellm/team.d.ts +67 -0
  145. package/dist/litellm/team.d.ts.map +1 -0
  146. package/dist/litellm/tool-policy-form.d.ts +21 -0
  147. package/dist/litellm/tool-policy-form.d.ts.map +1 -0
  148. package/dist/litellm/tool-policy-operations.d.ts +30 -0
  149. package/dist/litellm/tool-policy-operations.d.ts.map +1 -0
  150. package/dist/litellm/tool-policy-types.d.ts +46 -0
  151. package/dist/litellm/tool-policy-types.d.ts.map +1 -0
  152. package/dist/litellm/tool-policy.d.ts +62 -0
  153. package/dist/litellm/tool-policy.d.ts.map +1 -0
  154. package/dist/litellm/toolset-form.d.ts +20 -0
  155. package/dist/litellm/toolset-form.d.ts.map +1 -0
  156. package/dist/litellm/toolset-operations.d.ts +39 -0
  157. package/dist/litellm/toolset-operations.d.ts.map +1 -0
  158. package/dist/litellm/toolset-types.d.ts +46 -0
  159. package/dist/litellm/toolset-types.d.ts.map +1 -0
  160. package/dist/litellm/toolset.d.ts +55 -0
  161. package/dist/litellm/toolset.d.ts.map +1 -0
  162. package/dist/openbao/index.js +35 -1
  163. package/dist/openbao/index.js.map +3 -3
  164. package/dist/ownership/rows.d.ts +11 -0
  165. package/dist/ownership/rows.d.ts.map +1 -1
  166. package/dist/postgres/connection.d.ts +14 -2
  167. package/dist/postgres/connection.d.ts.map +1 -1
  168. package/dist/postgres/database-sql.d.ts +27 -8
  169. package/dist/postgres/database-sql.d.ts.map +1 -1
  170. package/dist/postgres/fake-grants-parse.d.ts +37 -0
  171. package/dist/postgres/fake-grants-parse.d.ts.map +1 -0
  172. package/dist/postgres/fake-grants-read.d.ts +61 -0
  173. package/dist/postgres/fake-grants-read.d.ts.map +1 -0
  174. package/dist/postgres/fake-grants-sql.d.ts +46 -0
  175. package/dist/postgres/fake-grants-sql.d.ts.map +1 -0
  176. package/dist/postgres/fake-grants-write.d.ts +29 -0
  177. package/dist/postgres/fake-grants-write.d.ts.map +1 -0
  178. package/dist/postgres/fake-role-sql.d.ts +41 -0
  179. package/dist/postgres/fake-role-sql.d.ts.map +1 -0
  180. package/dist/postgres/fake-schema-sql.d.ts +42 -0
  181. package/dist/postgres/fake-schema-sql.d.ts.map +1 -0
  182. package/dist/postgres/fake-sql-parse.d.ts +23 -0
  183. package/dist/postgres/fake-sql-parse.d.ts.map +1 -0
  184. package/dist/postgres/fake-sql-quote.d.ts +12 -0
  185. package/dist/postgres/fake-sql-quote.d.ts.map +1 -0
  186. package/dist/postgres/fake-sql.d.ts +42 -0
  187. package/dist/postgres/fake-sql.d.ts.map +1 -1
  188. package/dist/postgres/grants-attrs.d.ts +115 -0
  189. package/dist/postgres/grants-attrs.d.ts.map +1 -0
  190. package/dist/postgres/grants-declare.d.ts +88 -0
  191. package/dist/postgres/grants-declare.d.ts.map +1 -0
  192. package/dist/postgres/grants-diff.d.ts +25 -0
  193. package/dist/postgres/grants-diff.d.ts.map +1 -0
  194. package/dist/postgres/grants-errors.d.ts +137 -0
  195. package/dist/postgres/grants-errors.d.ts.map +1 -0
  196. package/dist/postgres/grants-existence.d.ts +14 -0
  197. package/dist/postgres/grants-existence.d.ts.map +1 -0
  198. package/dist/postgres/grants-ops.d.ts +65 -0
  199. package/dist/postgres/grants-ops.d.ts.map +1 -0
  200. package/dist/postgres/grants-ownership.d.ts +18 -0
  201. package/dist/postgres/grants-ownership.d.ts.map +1 -0
  202. package/dist/postgres/grants-plan-restore.d.ts +22 -0
  203. package/dist/postgres/grants-plan-restore.d.ts.map +1 -0
  204. package/dist/postgres/grants-plan.d.ts +65 -0
  205. package/dist/postgres/grants-plan.d.ts.map +1 -0
  206. package/dist/postgres/grants-read-defaults.d.ts +17 -0
  207. package/dist/postgres/grants-read-defaults.d.ts.map +1 -0
  208. package/dist/postgres/grants-read.d.ts +77 -0
  209. package/dist/postgres/grants-read.d.ts.map +1 -0
  210. package/dist/postgres/grants-refuse.d.ts +25 -0
  211. package/dist/postgres/grants-refuse.d.ts.map +1 -0
  212. package/dist/postgres/grants-sql.d.ts +42 -0
  213. package/dist/postgres/grants-sql.d.ts.map +1 -0
  214. package/dist/postgres/grants-words.d.ts +29 -0
  215. package/dist/postgres/grants-words.d.ts.map +1 -0
  216. package/dist/postgres/grants.d.ts +37 -0
  217. package/dist/postgres/grants.d.ts.map +1 -0
  218. package/dist/postgres/index.d.ts +31 -6
  219. package/dist/postgres/index.d.ts.map +1 -1
  220. package/dist/postgres/index.js +1998 -37
  221. package/dist/postgres/index.js.map +46 -7
  222. package/dist/postgres/installed-classifier.d.ts +13 -0
  223. package/dist/postgres/installed-classifier.d.ts.map +1 -0
  224. package/dist/postgres/providers.d.ts +16 -8
  225. package/dist/postgres/providers.d.ts.map +1 -1
  226. package/dist/postgres/psql-executor.d.ts +4 -0
  227. package/dist/postgres/psql-executor.d.ts.map +1 -1
  228. package/dist/postgres/role-attrs.d.ts +112 -0
  229. package/dist/postgres/role-attrs.d.ts.map +1 -0
  230. package/dist/postgres/role-diff.d.ts +22 -0
  231. package/dist/postgres/role-diff.d.ts.map +1 -0
  232. package/dist/postgres/role-errors.d.ts +132 -0
  233. package/dist/postgres/role-errors.d.ts.map +1 -0
  234. package/dist/postgres/role-membership-sql.d.ts +78 -0
  235. package/dist/postgres/role-membership-sql.d.ts.map +1 -0
  236. package/dist/postgres/role-provider.d.ts +11 -0
  237. package/dist/postgres/role-provider.d.ts.map +1 -0
  238. package/dist/postgres/role-scram.d.ts +8 -0
  239. package/dist/postgres/role-scram.d.ts.map +1 -0
  240. package/dist/postgres/role-secrets.d.ts +38 -0
  241. package/dist/postgres/role-secrets.d.ts.map +1 -0
  242. package/dist/postgres/role-sql.d.ts +68 -0
  243. package/dist/postgres/role-sql.d.ts.map +1 -0
  244. package/dist/postgres/role.d.ts +57 -0
  245. package/dist/postgres/role.d.ts.map +1 -0
  246. package/dist/postgres/schema-assert.d.ts +50 -0
  247. package/dist/postgres/schema-assert.d.ts.map +1 -0
  248. package/dist/postgres/schema-attrs.d.ts +51 -0
  249. package/dist/postgres/schema-attrs.d.ts.map +1 -0
  250. package/dist/postgres/schema-delete.d.ts +28 -0
  251. package/dist/postgres/schema-delete.d.ts.map +1 -0
  252. package/dist/postgres/schema-diff.d.ts +14 -0
  253. package/dist/postgres/schema-diff.d.ts.map +1 -0
  254. package/dist/postgres/schema-errors.d.ts +139 -0
  255. package/dist/postgres/schema-errors.d.ts.map +1 -0
  256. package/dist/postgres/schema-identity.d.ts +8 -0
  257. package/dist/postgres/schema-identity.d.ts.map +1 -0
  258. package/dist/postgres/schema-reconcile.d.ts +12 -0
  259. package/dist/postgres/schema-reconcile.d.ts.map +1 -0
  260. package/dist/postgres/schema-sql.d.ts +45 -0
  261. package/dist/postgres/schema-sql.d.ts.map +1 -0
  262. package/dist/postgres/schema-test-kit.d.ts +67 -0
  263. package/dist/postgres/schema-test-kit.d.ts.map +1 -0
  264. package/dist/postgres/schema.d.ts +54 -0
  265. package/dist/postgres/schema.d.ts.map +1 -0
  266. package/dist/proxmox/index.js +35 -1
  267. package/dist/proxmox/index.js.map +3 -3
  268. package/dist/release/fake-engine.d.ts +10 -0
  269. package/dist/release/fake-engine.d.ts.map +1 -1
  270. package/dist/release/index.js +35 -1
  271. package/dist/release/index.js.map +3 -3
  272. package/dist/valkey/acl-attrs.d.ts +94 -0
  273. package/dist/valkey/acl-attrs.d.ts.map +1 -0
  274. package/dist/valkey/acl-compare.d.ts +28 -0
  275. package/dist/valkey/acl-compare.d.ts.map +1 -0
  276. package/dist/valkey/acl-connection.d.ts +6 -0
  277. package/dist/valkey/acl-connection.d.ts.map +1 -0
  278. package/dist/valkey/acl-form.d.ts +62 -0
  279. package/dist/valkey/acl-form.d.ts.map +1 -0
  280. package/dist/valkey/acl-managed.d.ts +4 -0
  281. package/dist/valkey/acl-managed.d.ts.map +1 -0
  282. package/dist/valkey/acl-ops.d.ts +37 -0
  283. package/dist/valkey/acl-ops.d.ts.map +1 -0
  284. package/dist/valkey/acl-profiles.d.ts +53 -0
  285. package/dist/valkey/acl-profiles.d.ts.map +1 -0
  286. package/dist/valkey/acl-reconcile.d.ts +26 -0
  287. package/dist/valkey/acl-reconcile.d.ts.map +1 -0
  288. package/dist/valkey/acl.d.ts +24 -0
  289. package/dist/valkey/acl.d.ts.map +1 -0
  290. package/dist/valkey/connection-service.d.ts +22 -0
  291. package/dist/valkey/connection-service.d.ts.map +1 -0
  292. package/dist/valkey/connection.d.ts +59 -0
  293. package/dist/valkey/connection.d.ts.map +1 -0
  294. package/dist/valkey/errors.d.ts +165 -0
  295. package/dist/valkey/errors.d.ts.map +1 -0
  296. package/dist/valkey/fake-acl.d.ts +29 -0
  297. package/dist/valkey/fake-acl.d.ts.map +1 -0
  298. package/dist/valkey/fake-valkey.d.ts +35 -0
  299. package/dist/valkey/fake-valkey.d.ts.map +1 -0
  300. package/dist/valkey/handler-test-fixture.d.ts +20 -0
  301. package/dist/valkey/handler-test-fixture.d.ts.map +1 -0
  302. package/dist/valkey/index.d.ts +18 -0
  303. package/dist/valkey/index.d.ts.map +1 -0
  304. package/dist/valkey/index.js +964 -0
  305. package/dist/valkey/index.js.map +27 -0
  306. package/dist/valkey/instance-attrs.d.ts +46 -0
  307. package/dist/valkey/instance-attrs.d.ts.map +1 -0
  308. package/dist/valkey/instance-form.d.ts +22 -0
  309. package/dist/valkey/instance-form.d.ts.map +1 -0
  310. package/dist/valkey/instance.d.ts +48 -0
  311. package/dist/valkey/instance.d.ts.map +1 -0
  312. package/dist/valkey/provider-socket-fixture.d.ts +8 -0
  313. package/dist/valkey/provider-socket-fixture.d.ts.map +1 -0
  314. package/dist/valkey/providers.d.ts +16 -0
  315. package/dist/valkey/providers.d.ts.map +1 -0
  316. package/dist/valkey/resp.d.ts +38 -0
  317. package/dist/valkey/resp.d.ts.map +1 -0
  318. package/dist/valkey/transport-error.d.ts +36 -0
  319. package/dist/valkey/transport-error.d.ts.map +1 -0
  320. package/dist/valkey/transport.d.ts +50 -0
  321. package/dist/valkey/transport.d.ts.map +1 -0
  322. package/docs/litellm-access-group.md +105 -0
  323. package/docs/litellm-credential.md +60 -0
  324. package/docs/litellm-key-metadata.md +56 -0
  325. package/docs/litellm-key.md +194 -0
  326. package/docs/litellm-mcp.md +187 -0
  327. package/docs/litellm-model.md +111 -0
  328. package/docs/litellm-policy-attachment.md +86 -0
  329. package/docs/litellm-policy.md +103 -0
  330. package/docs/litellm-team.md +117 -0
  331. package/docs/litellm-tool-policy.md +90 -0
  332. package/docs/litellm-toolset.md +92 -0
  333. package/docs/litellm.md +16 -9
  334. package/docs/peers.md +15 -6
  335. package/docs/postgres-grants-example.md +55 -0
  336. package/docs/postgres-grants.md +157 -0
  337. package/docs/postgres-role.md +126 -0
  338. package/docs/postgres-schema.md +139 -0
  339. package/docs/postgres.md +20 -3
  340. package/docs/valkey-transport.md +29 -0
  341. package/docs/valkey.md +187 -0
  342. package/package.json +6 -2
  343. package/src/ceph/ceph-argv.test.ts +1 -1
  344. package/src/launchd/sudo-allowlist.test.ts +2 -2
  345. package/src/linux/sudo-allowlist.test.ts +3 -3
  346. package/src/linux/sudo-runner.test.ts +2 -2
  347. package/src/litellm/access-group-form.ts +105 -0
  348. package/src/litellm/access-group-operations.ts +92 -0
  349. package/src/litellm/access-group-types.ts +49 -0
  350. package/src/litellm/access-group.test.ts +197 -0
  351. package/src/litellm/access-group.ts +151 -0
  352. package/src/litellm/credential-errors.ts +183 -0
  353. package/src/litellm/credential-form.test.ts +151 -0
  354. package/src/litellm/credential-form.ts +211 -0
  355. package/src/litellm/credential-guards.test.ts +241 -0
  356. package/src/litellm/credential-operations.ts +208 -0
  357. package/src/litellm/credential-recovery.test.ts +93 -0
  358. package/src/litellm/credential-rewrite.test.ts +164 -0
  359. package/src/litellm/credential-rewrite.ts +50 -0
  360. package/src/litellm/credential-types.ts +117 -0
  361. package/src/litellm/credential-values.ts +94 -0
  362. package/src/litellm/credential.test.ts +218 -0
  363. package/src/litellm/credential.ts +227 -0
  364. package/src/litellm/fake-access-group-litellm.ts +127 -0
  365. package/src/litellm/fake-credential-litellm.ts +216 -0
  366. package/src/litellm/fake-keys.ts +190 -0
  367. package/src/litellm/fake-litellm.ts +19 -9
  368. package/src/litellm/fake-mcp-litellm.ts +214 -0
  369. package/src/litellm/fake-model-litellm-post-update.ts +103 -0
  370. package/src/litellm/fake-model-litellm.ts +233 -0
  371. package/src/litellm/fake-policy-attachment-litellm.ts +97 -0
  372. package/src/litellm/fake-policy-litellm.ts +186 -0
  373. package/src/litellm/fake-registry-base.ts +69 -0
  374. package/src/litellm/fake-stack.ts +17 -1
  375. package/src/litellm/fake-team-litellm.ts +229 -0
  376. package/src/litellm/fake-tool-litellm.ts +88 -0
  377. package/src/litellm/fake-toolset-litellm.ts +110 -0
  378. package/src/litellm/index.ts +140 -0
  379. package/src/litellm/key-delete.test.ts +212 -0
  380. package/src/litellm/key-errors.ts +241 -0
  381. package/src/litellm/key-form.ts +200 -0
  382. package/src/litellm/key-harness.ts +121 -0
  383. package/src/litellm/key-metadata-errors.ts +49 -0
  384. package/src/litellm/key-metadata.test.ts +156 -0
  385. package/src/litellm/key-metadata.ts +74 -0
  386. package/src/litellm/key-operations.ts +184 -0
  387. package/src/litellm/key-rotation.test.ts +110 -0
  388. package/src/litellm/key-scope.test.ts +135 -0
  389. package/src/litellm/key-secret.test.ts +248 -0
  390. package/src/litellm/key-secret.ts +137 -0
  391. package/src/litellm/key-transport.test.ts +166 -0
  392. package/src/litellm/key.test.ts +231 -0
  393. package/src/litellm/key.ts +213 -0
  394. package/src/litellm/mcp-server-adopt.test.ts +86 -0
  395. package/src/litellm/mcp-server-alias.test.ts +184 -0
  396. package/src/litellm/mcp-server-credential.ts +85 -0
  397. package/src/litellm/mcp-server-errors.ts +141 -0
  398. package/src/litellm/mcp-server-exports.test.ts +15 -0
  399. package/src/litellm/mcp-server-form.test.ts +241 -0
  400. package/src/litellm/mcp-server-form.ts +233 -0
  401. package/src/litellm/mcp-server-handlers.test.ts +150 -0
  402. package/src/litellm/mcp-server-locate.ts +66 -0
  403. package/src/litellm/mcp-server-operations.ts +111 -0
  404. package/src/litellm/mcp-server-registry.test.ts +146 -0
  405. package/src/litellm/mcp-server-retype.test.ts +114 -0
  406. package/src/litellm/mcp-server-shadow.test.ts +91 -0
  407. package/src/litellm/mcp-server-shadow.ts +45 -0
  408. package/src/litellm/mcp-server-tools.test.ts +85 -0
  409. package/src/litellm/mcp-server-types.ts +143 -0
  410. package/src/litellm/mcp-server-url.ts +66 -0
  411. package/src/litellm/mcp-server.test.ts +213 -0
  412. package/src/litellm/mcp-server.ts +225 -0
  413. package/src/litellm/model-claim.ts +69 -0
  414. package/src/litellm/model-credential.ts +45 -0
  415. package/src/litellm/model-errors.ts +168 -0
  416. package/src/litellm/model-foreign-row.test.ts +173 -0
  417. package/src/litellm/model-form.test.ts +228 -0
  418. package/src/litellm/model-form.ts +223 -0
  419. package/src/litellm/model-locate.test.ts +128 -0
  420. package/src/litellm/model-locate.ts +67 -0
  421. package/src/litellm/model-operations.test.ts +93 -0
  422. package/src/litellm/model-operations.ts +146 -0
  423. package/src/litellm/model-params-preserve.test.ts +119 -0
  424. package/src/litellm/model-recovery.test.ts +57 -0
  425. package/src/litellm/model-types.ts +112 -0
  426. package/src/litellm/model.test.ts +222 -0
  427. package/src/litellm/model.ts +237 -0
  428. package/src/litellm/policy-attachment-form.ts +103 -0
  429. package/src/litellm/policy-attachment-operations.ts +74 -0
  430. package/src/litellm/policy-attachment-types.ts +48 -0
  431. package/src/litellm/policy-attachment.test.ts +198 -0
  432. package/src/litellm/policy-attachment.ts +178 -0
  433. package/src/litellm/policy-form.ts +122 -0
  434. package/src/litellm/policy-operations.ts +112 -0
  435. package/src/litellm/policy-types.ts +58 -0
  436. package/src/litellm/policy.test.ts +214 -0
  437. package/src/litellm/policy.ts +146 -0
  438. package/src/litellm/providers.ts +24 -3
  439. package/src/litellm/registry-errors.ts +88 -0
  440. package/src/litellm/registry-exports.test.ts +66 -0
  441. package/src/litellm/registry-support.ts +109 -0
  442. package/src/litellm/team-form.ts +140 -0
  443. package/src/litellm/team-members.test.ts +150 -0
  444. package/src/litellm/team-members.ts +58 -0
  445. package/src/litellm/team-operations.ts +90 -0
  446. package/src/litellm/team-permission.ts +159 -0
  447. package/src/litellm/team-read.test.ts +89 -0
  448. package/src/litellm/team-types.ts +106 -0
  449. package/src/litellm/team.test.ts +206 -0
  450. package/src/litellm/team.ts +159 -0
  451. package/src/litellm/tool-policy-form.ts +94 -0
  452. package/src/litellm/tool-policy-operations.ts +58 -0
  453. package/src/litellm/tool-policy-types.ts +48 -0
  454. package/src/litellm/tool-policy.test.ts +172 -0
  455. package/src/litellm/tool-policy.ts +125 -0
  456. package/src/litellm/toolset-form.ts +104 -0
  457. package/src/litellm/toolset-operations.ts +90 -0
  458. package/src/litellm/toolset-types.ts +47 -0
  459. package/src/litellm/toolset.test.ts +198 -0
  460. package/src/litellm/toolset.ts +151 -0
  461. package/src/ownership/ownership.test.ts +33 -1
  462. package/src/ownership/rows.ts +53 -0
  463. package/src/postgres/connection.ts +42 -7
  464. package/src/postgres/database-bigint-serialization.test.ts +1 -0
  465. package/src/postgres/database-refusals.test.ts +2 -2
  466. package/src/postgres/database-sql.ts +36 -9
  467. package/src/postgres/fake-grants-parse.ts +189 -0
  468. package/src/postgres/fake-grants-read.ts +250 -0
  469. package/src/postgres/fake-grants-sql.ts +207 -0
  470. package/src/postgres/fake-grants-write.ts +90 -0
  471. package/src/postgres/fake-role-sql.ts +240 -0
  472. package/src/postgres/fake-schema-semantics.test.ts +36 -0
  473. package/src/postgres/fake-schema-sql.ts +146 -0
  474. package/src/postgres/fake-sql-parse.ts +86 -0
  475. package/src/postgres/fake-sql-quote.ts +36 -0
  476. package/src/postgres/fake-sql.ts +166 -46
  477. package/src/postgres/fixtures/pg-REL_18_6-acl.h.txt +147 -0
  478. package/src/postgres/fixtures/pg-REL_18_6-aclexplode-func.txt +31 -0
  479. package/src/postgres/fixtures/pg-REL_18_6-alter-default-privileges-synopsis.txt +82 -0
  480. package/src/postgres/fixtures/pg-REL_18_6-alter_role.sgml.txt +44 -0
  481. package/src/postgres/fixtures/pg-REL_18_6-create_role.sgml.txt +26 -0
  482. package/src/postgres/fixtures/pg-REL_18_6-grant-synopsis.txt +95 -0
  483. package/src/postgres/fixtures/pg-REL_18_6-pg_attribute.h.txt +24 -0
  484. package/src/postgres/fixtures/pg-REL_18_6-pg_auth_members.h.txt +62 -0
  485. package/src/postgres/fixtures/pg-REL_18_6-pg_authid.h.txt +70 -0
  486. package/src/postgres/fixtures/pg-REL_18_6-pg_class.h.txt +33 -0
  487. package/src/postgres/fixtures/pg-REL_18_6-pg_default_acl.h.txt +81 -0
  488. package/src/postgres/fixtures/pg-REL_18_6-pg_namespace.h.txt +28 -0
  489. package/src/postgres/fixtures/pg-REL_18_6-revoke-synopsis.txt +124 -0
  490. package/src/postgres/grants-acl.test.ts +61 -0
  491. package/src/postgres/grants-attrs.ts +185 -0
  492. package/src/postgres/grants-database.test.ts +158 -0
  493. package/src/postgres/grants-declare.ts +172 -0
  494. package/src/postgres/grants-delete-proof.test.ts +154 -0
  495. package/src/postgres/grants-diff.ts +101 -0
  496. package/src/postgres/grants-errors.ts +215 -0
  497. package/src/postgres/grants-existence-guards.test.ts +113 -0
  498. package/src/postgres/grants-existence.ts +101 -0
  499. package/src/postgres/grants-lifecycle-guards.test.ts +208 -0
  500. package/src/postgres/grants-lifecycle-repair.test.ts +208 -0
  501. package/src/postgres/grants-lifecycle.test.ts +181 -0
  502. package/src/postgres/grants-ops.ts +229 -0
  503. package/src/postgres/grants-owner-revoke.test.ts +121 -0
  504. package/src/postgres/grants-ownership.ts +49 -0
  505. package/src/postgres/grants-plan-collateral.test.ts +202 -0
  506. package/src/postgres/grants-plan-diff.test.ts +204 -0
  507. package/src/postgres/grants-plan-restore.ts +43 -0
  508. package/src/postgres/grants-plan-revocations.test.ts +69 -0
  509. package/src/postgres/grants-plan.test.ts +148 -0
  510. package/src/postgres/grants-plan.ts +245 -0
  511. package/src/postgres/grants-provenance.test.ts +134 -0
  512. package/src/postgres/grants-psql-lifecycle.test.ts +105 -0
  513. package/src/postgres/grants-read-absent.test.ts +91 -0
  514. package/src/postgres/grants-read-defaults.ts +51 -0
  515. package/src/postgres/grants-read.ts +250 -0
  516. package/src/postgres/grants-refusals.test.ts +136 -0
  517. package/src/postgres/grants-refuse.ts +119 -0
  518. package/src/postgres/grants-relkind-wire.test.ts +111 -0
  519. package/src/postgres/grants-repair-plan.test.ts +201 -0
  520. package/src/postgres/grants-sql.test.ts +128 -0
  521. package/src/postgres/grants-sql.ts +144 -0
  522. package/src/postgres/grants-words.ts +34 -0
  523. package/src/postgres/grants.ts +110 -0
  524. package/src/postgres/index.ts +129 -5
  525. package/src/postgres/installed-classifier.ts +36 -0
  526. package/src/postgres/providers.ts +33 -9
  527. package/src/postgres/psql-executor-escaping.test.ts +67 -0
  528. package/src/postgres/psql-executor.test.ts +63 -2
  529. package/src/postgres/psql-executor.ts +74 -34
  530. package/src/postgres/role-adopt.test.ts +98 -0
  531. package/src/postgres/role-attrs.ts +137 -0
  532. package/src/postgres/role-diff.ts +93 -0
  533. package/src/postgres/role-errors.ts +206 -0
  534. package/src/postgres/role-guard.test.ts +60 -0
  535. package/src/postgres/role-identity.test.ts +197 -0
  536. package/src/postgres/role-lifecycle.test.ts +228 -0
  537. package/src/postgres/role-membership-options.test.ts +73 -0
  538. package/src/postgres/role-membership-repair.test.ts +153 -0
  539. package/src/postgres/role-membership-sql.ts +223 -0
  540. package/src/postgres/role-password-ascii.test.ts +53 -0
  541. package/src/postgres/role-password-transports.test.ts +123 -0
  542. package/src/postgres/role-password.test.ts +158 -0
  543. package/src/postgres/role-privilege.test.ts +107 -0
  544. package/src/postgres/role-provenance.test.ts +200 -0
  545. package/src/postgres/role-provider.ts +105 -0
  546. package/src/postgres/role-refusals.test.ts +225 -0
  547. package/src/postgres/role-scram.test.ts +38 -0
  548. package/src/postgres/role-scram.ts +40 -0
  549. package/src/postgres/role-secrets.ts +61 -0
  550. package/src/postgres/role-sql.ts +213 -0
  551. package/src/postgres/role-valid-until.test.ts +68 -0
  552. package/src/postgres/role.ts +226 -0
  553. package/src/postgres/schema-assert.ts +139 -0
  554. package/src/postgres/schema-attrs.ts +71 -0
  555. package/src/postgres/schema-delete-proofs.test.ts +192 -0
  556. package/src/postgres/schema-delete.ts +96 -0
  557. package/src/postgres/schema-diff.ts +44 -0
  558. package/src/postgres/schema-drop-2bp01.test.ts +146 -0
  559. package/src/postgres/schema-errors.ts +209 -0
  560. package/src/postgres/schema-handlers.test.ts +193 -0
  561. package/src/postgres/schema-identity.ts +39 -0
  562. package/src/postgres/schema-lifecycle.test.ts +246 -0
  563. package/src/postgres/schema-race.test.ts +70 -0
  564. package/src/postgres/schema-reconcile.ts +100 -0
  565. package/src/postgres/schema-refusals.test.ts +144 -0
  566. package/src/postgres/schema-socket.test.ts +162 -0
  567. package/src/postgres/schema-sql.ts +130 -0
  568. package/src/postgres/schema-takeover.test.ts +149 -0
  569. package/src/postgres/schema-test-kit.ts +85 -0
  570. package/src/postgres/schema.ts +130 -0
  571. package/src/release/binary-alias.test.ts +3 -3
  572. package/src/release/binary-claims.test.ts +3 -3
  573. package/src/release/binary-directory-umask.test.ts +65 -0
  574. package/src/release/fake-engine.ts +15 -0
  575. package/src/valkey/acl-attrs.ts +98 -0
  576. package/src/valkey/acl-compare.ts +57 -0
  577. package/src/valkey/acl-connection.ts +13 -0
  578. package/src/valkey/acl-delete.test.ts +127 -0
  579. package/src/valkey/acl-form.ts +239 -0
  580. package/src/valkey/acl-managed.ts +13 -0
  581. package/src/valkey/acl-ops.ts +122 -0
  582. package/src/valkey/acl-ownership.test.ts +118 -0
  583. package/src/valkey/acl-prefix.test.ts +38 -0
  584. package/src/valkey/acl-profiles.ts +81 -0
  585. package/src/valkey/acl-reconcile.ts +180 -0
  586. package/src/valkey/acl-reserved.test.ts +69 -0
  587. package/src/valkey/acl-revocation.test.ts +94 -0
  588. package/src/valkey/acl-seat-prefix.test.ts +111 -0
  589. package/src/valkey/acl-validation.test.ts +54 -0
  590. package/src/valkey/acl.test.ts +215 -0
  591. package/src/valkey/acl.ts +169 -0
  592. package/src/valkey/connection-service.ts +46 -0
  593. package/src/valkey/connection.ts +180 -0
  594. package/src/valkey/errors.ts +239 -0
  595. package/src/valkey/fake-acl.ts +128 -0
  596. package/src/valkey/fake-valkey.ts +224 -0
  597. package/src/valkey/handler-test-fixture.ts +43 -0
  598. package/src/valkey/handlers.test.ts +205 -0
  599. package/src/valkey/index.ts +54 -0
  600. package/src/valkey/instance-attrs.ts +48 -0
  601. package/src/valkey/instance-form.ts +104 -0
  602. package/src/valkey/instance.test.ts +108 -0
  603. package/src/valkey/instance.ts +160 -0
  604. package/src/valkey/provider-socket-fixture.ts +71 -0
  605. package/src/valkey/providers.test.ts +129 -0
  606. package/src/valkey/providers.ts +21 -0
  607. package/src/valkey/resp.ts +212 -0
  608. package/src/valkey/transport-bounds.test.ts +150 -0
  609. package/src/valkey/transport-error.ts +37 -0
  610. package/src/valkey/transport.test.ts +158 -0
  611. package/src/valkey/transport.ts +109 -0
  612. package/src/valkey/upstream-bounds.test.ts +14 -0
@@ -0,0 +1,198 @@
1
+ /**
2
+ * `LiteLLM.Toolset` through Alchemy's real Plan and Apply over the fake proxy
3
+ * (`fake-toolset-litellm.ts`): create, no-op, adopt by name, update, rename and delete.
4
+ *
5
+ * ★ EVERY VALUE IS `FAKE-*`.
6
+ * ⚠️ WHAT THE FAKE MODELS IS READ FROM THE 1.103.0 SOURCE, NOT MEASURED ON A LIVE PROXY (its header).
7
+ */
8
+ import { expect, test } from 'bun:test';
9
+ import { credentials } from '@distilled.cloud/litellm/Credentials';
10
+ import * as Effect from 'effect/Effect';
11
+ import * as Layer from 'effect/Layer';
12
+ import * as FetchHttpClient from 'effect/unstable/http/FetchHttpClient';
13
+ import { FAKE_BASE } from './fake-litellm.ts';
14
+ import { FAKE_KEY } from './fake-registry-base.ts';
15
+ import {
16
+ type FakeToolsetLitellm,
17
+ startFakeToolsetLitellm,
18
+ toolsetRow,
19
+ } from './fake-toolset-litellm.ts';
20
+ import { fakeStack, writesOf } from './fake-stack.ts';
21
+ import { LiteLLMToolset } from './toolset.ts';
22
+ import { deleteToolset } from './toolset-operations.ts';
23
+ import type { ToolsetProps } from './toolset-types.ts';
24
+
25
+ const stack = (fake: FakeToolsetLitellm) =>
26
+ fakeStack({ apiKey: FAKE_KEY, baseUrl: FAKE_BASE }, fake.fetch);
27
+ const declare = (props: ToolsetProps, name = 'Web') =>
28
+ Effect.gen(function* () {
29
+ yield* LiteLLMToolset(name, props);
30
+ });
31
+
32
+ const web: ToolsetProps = {
33
+ description: 'FAKE web tools',
34
+ tools: [
35
+ { serverId: 'FAKE-server-a', toolName: 'search' },
36
+ { serverId: 'FAKE-server-b', toolName: 'fetch' },
37
+ ],
38
+ toolsetName: 'FAKE-web',
39
+ };
40
+
41
+ test('creates a toolset with the pairs, remembers the id the proxy issued, then writes nothing', async () => {
42
+ const fake = startFakeToolsetLitellm();
43
+ const same = stack(fake);
44
+ expect(await same.deploy(declare(web))).toEqual({ Web: 'create' });
45
+ expect(fake.toolsets()[0]).toMatchObject({
46
+ description: 'FAKE web tools',
47
+ tools: web.tools.map((tool) => ({ server_id: tool.serverId, tool_name: tool.toolName })),
48
+ toolset_name: 'FAKE-web',
49
+ });
50
+ const before = fake.requests().length;
51
+ expect(await same.deploy(declare(web))).toEqual({ Web: 'noop' });
52
+ expect(writesOf(fake.requests().slice(before))).toEqual([]);
53
+ });
54
+
55
+ test('the list is used ONLY to adopt: with state, every read is by id and the list is never asked', async () => {
56
+ const fake = startFakeToolsetLitellm();
57
+ const same = stack(fake);
58
+ await same.deploy(declare(web));
59
+ const before = fake.requests().length;
60
+ await same.deploy(declare({ ...web, description: 'FAKE changed' }));
61
+ const paths = fake
62
+ .requests()
63
+ .slice(before)
64
+ .map((each) => `${each.method} ${each.path}`);
65
+ expect(paths).not.toContain('GET /v1/mcp/toolset');
66
+ expect(paths.some((path) => path.startsWith('GET /v1/mcp/toolset/FAKE-toolset-'))).toBe(true);
67
+ });
68
+
69
+ test('a changed selection is an update that sends the id and ONLY the changed field', async () => {
70
+ const fake = startFakeToolsetLitellm();
71
+ const same = stack(fake);
72
+ await same.deploy(declare(web));
73
+ const planned = await same.deploy(
74
+ declare({ ...web, tools: [{ serverId: 'FAKE-server-a', toolName: 'search' }] }),
75
+ );
76
+ expect(planned).toEqual({ Web: 'update' });
77
+ expect(fake.bodies().at(-1)).toEqual({
78
+ tools: [{ server_id: 'FAKE-server-a', tool_name: 'search' }],
79
+ toolset_id: 'FAKE-toolset-0001',
80
+ });
81
+ });
82
+
83
+ test('an empty selection is sent as an explicit empty list, because a null would be a no-op', async () => {
84
+ const fake = startFakeToolsetLitellm();
85
+ const same = stack(fake);
86
+ await same.deploy(declare(web));
87
+ await same.deploy(declare({ ...web, tools: [] }));
88
+ expect(fake.bodies().at(-1)).toMatchObject({ tools: [] });
89
+ expect(fake.toolsets()[0]?.['tools']).toEqual([]);
90
+ });
91
+
92
+ test('a rename is an update of the SAME row', async () => {
93
+ const fake = startFakeToolsetLitellm();
94
+ const same = stack(fake);
95
+ await same.deploy(declare(web));
96
+ expect(await same.deploy(declare({ ...web, toolsetName: 'FAKE-renamed' }))).toEqual({
97
+ Web: 'update',
98
+ });
99
+ expect(fake.toolsets()).toHaveLength(1);
100
+ expect(fake.toolsets()[0]).toMatchObject({
101
+ toolset_id: 'FAKE-toolset-0001',
102
+ toolset_name: 'FAKE-renamed',
103
+ });
104
+ });
105
+
106
+ test('a live row is Unowned: refused without --adopt, adopted by name with it, and a matching row writes nothing', async () => {
107
+ const live = toolsetRow({
108
+ description: 'FAKE web tools',
109
+ tools: [
110
+ { server_id: 'FAKE-server-b', tool_name: 'fetch' },
111
+ { server_id: 'FAKE-server-a', tool_name: 'search' },
112
+ ],
113
+ toolset_id: 'FAKE-live-1',
114
+ toolset_name: 'FAKE-web',
115
+ });
116
+ const fake = startFakeToolsetLitellm({ seed: [live] });
117
+ const engine = stack(fake);
118
+ await expect(engine.deploy(declare(web))).rejects.toThrow();
119
+ expect(writesOf(fake.requests())).toEqual([]);
120
+ expect(Object.values(await engine.deploy(declare(web), { adopt: true }))).toEqual(['adopted']);
121
+ expect(writesOf(fake.requests())).toEqual([]);
122
+ });
123
+
124
+ test('adopting a toolset with MORE tools than declared corrects it, and keeps undeclared text', async () => {
125
+ const wide = toolsetRow({
126
+ description: 'written by a person',
127
+ tools: [
128
+ { server_id: 'FAKE-server-a', tool_name: 'search' },
129
+ { server_id: 'FAKE-server-b', tool_name: 'fetch' },
130
+ { server_id: 'FAKE-server-b', tool_name: 'FAKE-extra' },
131
+ ],
132
+ toolset_id: 'FAKE-live-2',
133
+ toolset_name: 'FAKE-web',
134
+ });
135
+ const fake = startFakeToolsetLitellm({ seed: [wide] });
136
+ const { description: _kept, ...withoutDescription } = web;
137
+ await stack(fake).deploy(declare(withoutDescription), { adopt: true });
138
+ expect(fake.toolsets()[0]?.['tools']).toHaveLength(2);
139
+ expect(fake.toolsets()[0]?.['description']).toBe('written by a person');
140
+ expect(fake.bodies()[0]).not.toHaveProperty('description');
141
+ });
142
+
143
+ test('a list that swallowed a database error cannot hide an existing name: the create fails on the 409', async () => {
144
+ // ⛔ measured in the source: list_mcp_toolsets answers [] on any exception
145
+ const live = toolsetRow({ toolset_id: 'FAKE-live-3', toolset_name: 'FAKE-web' });
146
+ const fake = startFakeToolsetLitellm({ listSwallowsErrors: true, seed: [live] });
147
+ await expect(stack(fake).deploy(declare(web))).rejects.toThrow();
148
+ expect(fake.toolsets()).toHaveLength(1);
149
+ });
150
+
151
+ test('a refused declaration fails the plan before a single request', async () => {
152
+ for (const props of [
153
+ { ...web, toolsetName: ' ' },
154
+ { ...web, toolsetName: 'a/b' },
155
+ { ...web, description: ' ' },
156
+ { ...web, tools: [{ serverId: '', toolName: 'search' }] },
157
+ { ...web, tools: [web.tools[0], web.tools[0]] as never },
158
+ ]) {
159
+ const fake = startFakeToolsetLitellm();
160
+ await expect(stack(fake).deploy(declare(props))).rejects.toThrow();
161
+ expect(fake.requests()).toEqual([]);
162
+ }
163
+ });
164
+
165
+ test('removing the declaration RETAINS the toolset', async () => {
166
+ const fake = startFakeToolsetLitellm();
167
+ const same = stack(fake);
168
+ await same.deploy(declare(web));
169
+ await same.deploy(Effect.void);
170
+ expect(fake.toolsets()).toHaveLength(1);
171
+ });
172
+
173
+ const run = <A, E>(fetchFn: typeof globalThis.fetch, effect: Effect.Effect<A, E, unknown>) =>
174
+ Effect.runPromise(
175
+ effect.pipe(
176
+ Effect.provide(FetchHttpClient.layer),
177
+ Effect.provide(Layer.succeed(FetchHttpClient.Fetch, fetchFn)),
178
+ Effect.provide(credentials({ apiKey: FAKE_KEY, baseUrl: FAKE_BASE })),
179
+ ) as Effect.Effect<A, E, never>,
180
+ );
181
+
182
+ test('delete answers 202 with no body, and a missing id is swallowed only because a by-id read says so', async () => {
183
+ const fake = startFakeToolsetLitellm({
184
+ seed: [toolsetRow({ toolset_id: 'FAKE-live-4', toolset_name: 'FAKE-web' })],
185
+ });
186
+ await run(fake.fetch, deleteToolset('FAKE-live-4'));
187
+ expect(fake.toolsets()).toEqual([]);
188
+ await run(fake.fetch, deleteToolset('FAKE-live-4'));
189
+ });
190
+
191
+ test('a delete refused for lack of rights re-raises the ORIGINAL error and the row stays', async () => {
192
+ const fake = startFakeToolsetLitellm({
193
+ forbidWrites: true,
194
+ seed: [toolsetRow({ toolset_id: 'FAKE-live-5', toolset_name: 'FAKE-web' })],
195
+ });
196
+ await expect(run(fake.fetch, deleteToolset('FAKE-live-5'))).rejects.toThrow();
197
+ expect(fake.toolsets()).toHaveLength(1);
198
+ });
@@ -0,0 +1,151 @@
1
+ /**
2
+ * `LiteLLM.Toolset` — one row of LiteLLM's MCP toolset table (`/v1/mcp/toolset`): a named selection of
3
+ * `{ server_id, tool_name }` pairs a key or team can be granted instead of whole servers. Props and
4
+ * what is left out: toolset-types.ts.
5
+ *
6
+ * ★ ADOPT BY NAME. `toolset_name` is unique, so `toolsetName` finds the one live row (from the LIST,
7
+ * which is the only by-name lookup the API has, and which cannot prove absence: toolset-operations.ts).
8
+ * A live row with no state is `Unowned`, so it needs `--adopt`.
9
+ * ★ THE ID IS THE PROXY'S and is what state remembers; every later read is BY ID, from the table.
10
+ * ★ `defaultRemovalPolicy: 'retain'` — deleting a toolset does not detach it: teams and keys that hold
11
+ * its id keep a dangling grant, and every caller reaching MCP through it loses the tools.
12
+ * ⚠️ NO LICENCE GATE ON THESE ROUTES: `mcp_management_endpoints.py`'s toolset block has no
13
+ * `premium_user` check (1.103.0), and docs.litellm.ai/docs/mcp_toolsets makes no Enterprise statement.
14
+ * Writes need PROXY_ADMIN (403 otherwise).
15
+ * ⛔ NO LITELLM CREDENTIAL IS A PROP; `litellmProviders`' layer supplies `Credentials`.
16
+ */
17
+ import { Resource } from 'alchemy';
18
+ import { Unowned } from 'alchemy/AdoptPolicy';
19
+ import { isResolved } from 'alchemy/Diff';
20
+ import type { Input } from 'alchemy/Input';
21
+ import * as Provider from 'alchemy/Provider';
22
+ import * as Effect from 'effect/Effect';
23
+ import * as Predicate from 'effect/Predicate';
24
+ import {
25
+ LitellmRegistryAbsentAfterWriteError,
26
+ LitellmRegistryAmbiguousError,
27
+ LitellmRegistryNotConvergedError,
28
+ LitellmRegistryUnreadableError,
29
+ } from './registry-errors.ts';
30
+ import { dieIfInvalid, failIfInvalid } from './registry-support.ts';
31
+ import { createBody, differing, firstProblem, updateBody } from './toolset-form.ts';
32
+ import {
33
+ createToolset,
34
+ deleteToolset,
35
+ listToolsets,
36
+ readToolset,
37
+ updateToolset,
38
+ } from './toolset-operations.ts';
39
+ import type { ToolsetAttributes, ToolsetProps } from './toolset-types.ts';
40
+
41
+ export type { ToolsetAttributes, ToolsetProps };
42
+ export type { RegistryError as ToolsetError } from './registry-errors.ts';
43
+
44
+ const RESOURCE = 'LiteLLM.Toolset';
45
+
46
+ export interface LiteLLMToolset extends Resource<
47
+ 'LiteLLM.Toolset',
48
+ ToolsetProps,
49
+ ToolsetAttributes
50
+ > {}
51
+
52
+ export const LiteLLMToolset = Resource<LiteLLMToolset>('LiteLLM.Toolset', {
53
+ defaultRemovalPolicy: 'retain',
54
+ });
55
+
56
+ export const isLiteLLMToolset = (value: unknown): value is LiteLLMToolset =>
57
+ Predicate.hasProperty(value, 'Type') && value.Type === 'LiteLLM.Toolset';
58
+
59
+ /**
60
+ * The live row this declaration means: BY ID from the table when state has one (a vanished row is
61
+ * `undefined`, never taken over by name), else by name from the list. Two rows sharing a name would
62
+ * mean the unique index is gone; refused, never guessed.
63
+ */
64
+ const locate = (props: ToolsetProps, output: ToolsetAttributes | undefined) =>
65
+ Effect.gen(function* () {
66
+ if (output !== undefined) return yield* readToolset(output.toolsetId);
67
+ const named = (yield* listToolsets()).filter((row) => row.toolsetName === props.toolsetName);
68
+ if (named.length > 1) {
69
+ return yield* Effect.fail(
70
+ new LitellmRegistryAmbiguousError({
71
+ ids: named.map((row) => row.toolsetId),
72
+ name: props.toolsetName,
73
+ resource: RESOURCE,
74
+ }),
75
+ );
76
+ }
77
+ return named[0];
78
+ });
79
+
80
+ /** ⛔ TEST-ONLY EXPORT. Not on index.ts's barrel. */
81
+ export const toolsetHandlers = {
82
+ /** ⛔ With no `output` this is the adoption probe, so the refusals run here too (registry-support.ts). */
83
+ read: ({ olds, output }: { olds: ToolsetProps; output: ToolsetAttributes | undefined }) =>
84
+ Effect.gen(function* () {
85
+ if (output === undefined) {
86
+ yield* dieIfInvalid(RESOURCE, String(olds.toolsetName), firstProblem(olds));
87
+ }
88
+ const live = yield* locate(olds, output);
89
+ if (live === undefined) return undefined;
90
+ return output === undefined ? Unowned(live) : live;
91
+ }),
92
+
93
+ diff: ({ news, output }: { news: Input<ToolsetProps>; output: ToolsetAttributes | undefined }) =>
94
+ Effect.gen(function* () {
95
+ if (!isResolved(news)) return undefined;
96
+ yield* failIfInvalid(RESOURCE, String(news.toolsetName), firstProblem(news));
97
+ if (output === undefined) return undefined;
98
+ return differing(output, news).length === 0
99
+ ? ({ action: 'noop' } as const)
100
+ : ({ action: 'update' } as const);
101
+ }),
102
+
103
+ reconcile: ({ news, output }: { news: ToolsetProps; output: ToolsetAttributes | undefined }) =>
104
+ Effect.gen(function* () {
105
+ yield* failIfInvalid(RESOURCE, String(news.toolsetName), firstProblem(news));
106
+ const before = yield* locate(news, output);
107
+ let toolsetId: string;
108
+ if (before === undefined) {
109
+ const created = yield* createToolset(createBody(news));
110
+ if (created === undefined) {
111
+ return yield* Effect.fail(
112
+ new LitellmRegistryUnreadableError({
113
+ reason: 'the create answered something that is not a toolset',
114
+ resource: RESOURCE,
115
+ }),
116
+ );
117
+ }
118
+ toolsetId = created.toolsetId;
119
+ } else {
120
+ toolsetId = before.toolsetId;
121
+ if (differing(before, news).length > 0) yield* updateToolset(updateBody(news, before));
122
+ }
123
+
124
+ // ★ BY ID FROM THE TABLE, never the list: the list is the caller-narrowed, error-swallowing view.
125
+ const after = yield* readToolset(toolsetId);
126
+ if (after === undefined) {
127
+ return yield* Effect.fail(
128
+ new LitellmRegistryAbsentAfterWriteError({ name: news.toolsetName, resource: RESOURCE }),
129
+ );
130
+ }
131
+ const left = differing(after, news);
132
+ if (left.length > 0) {
133
+ return yield* Effect.fail(
134
+ new LitellmRegistryNotConvergedError({
135
+ fields: left,
136
+ name: news.toolsetName,
137
+ resource: RESOURCE,
138
+ }),
139
+ );
140
+ }
141
+ return after;
142
+ }),
143
+
144
+ delete: ({ output }: { output: ToolsetAttributes }) => deleteToolset(output.toolsetId),
145
+
146
+ /** ⛔ EMPTY: adoption is an explicit act (`--adopt`), and no ownership mark exists to filter by. */
147
+ list: () => Effect.succeed([]),
148
+ };
149
+
150
+ export const LiteLLMToolsetProvider = () =>
151
+ Provider.effect(LiteLLMToolset, Effect.succeed(LiteLLMToolset.Provider.of(toolsetHandlers)));
@@ -12,7 +12,7 @@ import * as Effect from 'effect/Effect';
12
12
  import { refuseTakeover } from './adopt.ts';
13
13
  import { ownedRead } from './probe.ts';
14
14
  import { noteResume, noteUnfinished, resumes } from './resume.ts';
15
- import { forgetRefusedCreate, isCreate, recordedInstance } from './rows.ts';
15
+ import { forgetRefusedCreate, isCreate, olderGenerationIds, recordedInstance } from './rows.ts';
16
16
  import { row, withStore } from './store-fixture.ts';
17
17
 
18
18
  describe('recordedInstance', () => {
@@ -50,6 +50,38 @@ describe('isCreate', () => {
50
50
  });
51
51
  });
52
52
 
53
+ describe('olderGenerationIds', () => {
54
+ test('collects attr.id from every generation older than the instance, through the old chain', async () => {
55
+ const rows = {
56
+ A: {
57
+ ...row('A', 'new', { ...row('A', 'older'), attr: { id: 'older-id' } }),
58
+ attr: { id: 'new-id' },
59
+ },
60
+ };
61
+ expect(await withStore(rows, olderGenerationIds('A', 'new'))).toEqual(['older-id']);
62
+ });
63
+
64
+ test('walks a former FQN the instance moved away from (renamedFrom)', async () => {
65
+ // Z is declared renamedFrom('X'). beta.79 moves the row (preserving instanceId) and deletes
66
+ // the former FQN; while a pre-move row still sits at X, the FormerFqns walk finds it and
67
+ // collects the older generation it holds.
68
+ const rows = {
69
+ Z: row('Z', 'new'),
70
+ X: {
71
+ ...row('X', 'new', { ...row('X', 'older'), attr: { id: 'older-id' } }),
72
+ attr: { id: 'new-id' },
73
+ },
74
+ };
75
+ expect(await withStore(rows, olderGenerationIds('Z', 'new'))).toEqual(['older-id']);
76
+ });
77
+
78
+ test('the current generation itself is skipped, and an empty store answers nothing', async () => {
79
+ const rows = { A: { ...row('A', 'new'), attr: { id: 'new-id' } } };
80
+ expect(await withStore(rows, olderGenerationIds('A', 'new'))).toEqual([]);
81
+ expect(await Effect.runPromise(olderGenerationIds('A', 'new'))).toEqual([]);
82
+ });
83
+ });
84
+
53
85
  describe('forgetRefusedCreate', () => {
54
86
  test('drops only the creating row of this instance, with no attributes', async () => {
55
87
  const rows: Record<string, ResourceState> = {
@@ -99,6 +99,59 @@ export const isCreate = (fqn: string, instanceId: string): Effect.Effect<boolean
99
99
  (generation) => generation?.status === 'creating',
100
100
  );
101
101
 
102
+ const idOf = (attr: unknown): string | undefined => {
103
+ if (typeof attr !== 'object' || attr === null) return undefined;
104
+ const id = (attr as { readonly id?: unknown }).id;
105
+ return typeof id === 'string' && id !== '' ? id : undefined;
106
+ };
107
+
108
+ /**
109
+ * `attr.id` on every generation OLDER than `instanceId` in this FQN's state chain (and a former
110
+ * FQN `renamedFrom` names). Walks `old` the way {@link generationOf} does, and skips the current
111
+ * generation itself.
112
+ * ⛔ THOSE IDS ARE NOT THIS GENERATION'S. A `replacing` row's reconcile runs with no attributes, so
113
+ * a live row found by name can be the previous generation's own deployment. Recording that id on
114
+ * the new generation makes both the same id, and `destroy()` then deletes the old generation by
115
+ * it (Apply.ts, the retain check near 2168 and the provider delete that follows when the policy
116
+ * is not `retain`). A same-instance `old` snapshot (an `updating` row) is not one of these.
117
+ */
118
+ export const olderGenerationIds = (
119
+ fqn: string,
120
+ instanceId: string,
121
+ ): Effect.Effect<readonly string[]> =>
122
+ Effect.gen(function* () {
123
+ const found = yield* storeOf;
124
+ if (found === undefined) return [];
125
+ const ids: string[] = [];
126
+ for (const at of [fqn, ...(found.resources[fqn]?.FormerFqns ?? [])]) {
127
+ collectOlder(yield* rowAt(found.store, found.where, at), instanceId, ids);
128
+ }
129
+ return ids;
130
+ });
131
+
132
+ /**
133
+ * Once `instanceId` has been seen, collect `attr.id` from generations with a DIFFERENT instance
134
+ * id. An `updating` row's `old` is the same instance's pre-update snapshot (Apply.ts), not a
135
+ * replacement generation — that snapshot's id is the row this apply is already writing.
136
+ */
137
+ const collectOlder = (row: unknown, instanceId: string, ids: string[]): void => {
138
+ let at = row;
139
+ let seen = false;
140
+ while (typeof at === 'object' && at !== null && !isActionState(at as never)) {
141
+ const generation = at as Generation;
142
+ if (!seen) {
143
+ seen = generation.instanceId === instanceId;
144
+ at = generation.old;
145
+ continue;
146
+ }
147
+ if (generation.instanceId !== instanceId) {
148
+ const id = idOf(generation.attr);
149
+ if (id !== undefined) ids.push(id);
150
+ }
151
+ at = generation.old;
152
+ }
153
+ };
154
+
102
155
  /**
103
156
  * ⛔ A REFUSED CREATE MUST NOT LEAVE A ROW THAT CLAIMS THE OBJECT IT REFUSED. Apply commits a
104
157
  * `creating` row for this instance before it calls reconcile, and a failed reconcile leaves it
@@ -44,9 +44,12 @@ export interface PostgresRunnerConfig {
44
44
  readonly template?: string;
45
45
  }
46
46
 
47
- /** What `withPg`'s callback learns about the transport beyond the client. */
47
+ /** What `withPg`'s callback learns about the transport beyond the client. `database` is the
48
+ * database the statements run IN: the family connection's, or the override `Postgres.Schema`
49
+ * and `Postgres.Grants` pass so they open their declared database and then prove it. */
48
50
  export interface PgContext {
49
51
  readonly template?: string;
52
+ readonly database: string;
50
53
  }
51
54
 
52
55
  /** The lazy connection service. Its value is an `Effect` of the config, per S24 — see the file
@@ -67,6 +70,11 @@ export const postgresRunnerConnection = (
67
70
  config: PostgresRunnerConfig,
68
71
  ): Layer.Layer<PostgresConnection> => Layer.succeed(PostgresConnection, Effect.succeed(config));
69
72
 
73
+ /** Effect-scoped pool factory, like BaoGate: tests override it without process-wide module mocks. */
74
+ export const PostgresPool = Context.Reference<typeof PgClient.layer>('homeflare/postgres/Pool', {
75
+ defaultValue: () => PgClient.layer,
76
+ });
77
+
70
78
  const isRunner = (c: PostgresConnectionConfig | PostgresRunnerConfig): c is PostgresRunnerConfig =>
71
79
  'run' in c;
72
80
 
@@ -77,20 +85,47 @@ const isRunner = (c: PostgresConnectionConfig | PostgresRunnerConfig): c is Post
77
85
  * operation — never once per statement — so a reconcile that runs a `SELECT`, then a `CREATE`,
78
86
  * then a re-read shares one pool and closes it once, while a plan-only `read` still opens and
79
87
  * closes its own.
88
+ *
89
+ * `database` overrides which database of the SAME cluster the pool opens — the declared
90
+ * database of `Postgres.Schema` and `Postgres.Grants`. The family connection is pointed at a
91
+ * maintenance database (a brand-new database cannot be connected to on a cold plan), so those
92
+ * resources pass their declared database here: the runner transport swaps the `psql -d` target,
93
+ * the socket path swaps the pool's `database`. The swap is proven, not trusted: schema rows
94
+ * carry `current_database()` (`schema-sql.ts`) and grants calls the same function before writing.
80
95
  */
81
96
  export const withPg = <A, E>(
82
- build: (pg: PgClient.PgClient | PgExecutor, context: PgContext) => Effect.Effect<A, E>,
97
+ build: (pg: PgExecutor, context: PgContext) => Effect.Effect<A, E>,
98
+ database?: string,
83
99
  ): Effect.Effect<A, E | SqlError, PostgresConnection> =>
84
100
  Effect.gen(function* () {
85
101
  const resolveConfig = yield* PostgresConnection;
86
102
  const config = yield* resolveConfig;
87
- if (isRunner(config)) {
88
- return yield* build(makePsqlExecutor(config.run, config), {
89
- template: config.template ?? 'template0',
103
+ const target: PostgresConnectionConfig | PostgresRunnerConfig =
104
+ database === undefined ? config : { ...config, database };
105
+ if (isRunner(target)) {
106
+ return yield* build(makePsqlExecutor(target.run, target), {
107
+ template: target.template ?? 'template0',
108
+ database: target.database,
90
109
  });
91
110
  }
111
+ const pool = yield* PostgresPool;
92
112
  return yield* Effect.provide(
93
- Effect.flatMap(PgClient.PgClient, (pg) => build(pg, {})),
94
- PgClient.layer(config),
113
+ Effect.flatMap(PgClient.PgClient, (pg) =>
114
+ build(
115
+ {
116
+ unsafe: (sql, params) => pg.unsafe(sql, params),
117
+ // One reserved connection (`SqlClient.withTransaction`). A pool checkout per
118
+ // statement would autocommit CREATE before GRANT.
119
+ transaction: (statements) =>
120
+ pg.withTransaction(
121
+ Effect.gen(function* () {
122
+ for (const sql of statements) yield* pg.unsafe(sql).pipe(Effect.asVoid);
123
+ }),
124
+ ),
125
+ },
126
+ { database: target.database },
127
+ ),
128
+ ),
129
+ pool(target),
95
130
  );
96
131
  });
@@ -90,6 +90,7 @@ const wireAccuratePg = (seed?: {
90
90
  }
91
91
  throw new Error(`wireAccuratePg: unrecognised statement: ${text}`);
92
92
  }) as Effect.Effect<ReadonlyArray<A>, SqlError>,
93
+ transaction: () => Effect.void,
93
94
  };
94
95
  };
95
96
 
@@ -50,12 +50,12 @@ describe('quoting', () => {
50
50
  });
51
51
 
52
52
  test('quoteStringLiteral doubles an embedded single quote', () => {
53
- expect(quoteStringLiteral("en_US.UTF-8'; DROP")).toBe("'en_US.UTF-8''; DROP'");
53
+ expect(quoteStringLiteral("en_US.UTF-8'; DROP")).toBe("E'en_US.UTF-8''; DROP'");
54
54
  });
55
55
 
56
56
  test('buildCreateDatabaseSql never string-concatenates an unescaped value', () => {
57
57
  const sql = buildCreateDatabaseSql({ name: 'db"1', owner: 'ro"le', encoding: "UT'F8" });
58
- expect(sql).toBe('CREATE DATABASE "db""1" WITH OWNER "ro""le" ENCODING \'UT\'\'F8\'');
58
+ expect(sql).toBe('CREATE DATABASE "db""1" WITH OWNER "ro""le" ENCODING E\'UT\'\'F8\'');
59
59
  });
60
60
  });
61
61
 
@@ -19,23 +19,33 @@
19
19
  * `createdb_opt_item`'s string branch is `opt_boolean_or_string → NonReservedWord_or_Sconst →
20
20
  * NonReservedWord | Sconst`. A locale like `en_US.UTF-8` would hit the same dot-splitting
21
21
  * `quoteIdent` avoids if it were identifier-quoted; a plain `Sconst` (`quoteStringLiteral`) has
22
- * no such rule. This rests on `standard_conforming_strings = on` (the PG18 default, and
23
- * measured live 2026-09-23 — `docs/postgres.md#measured`); the escaper only ever doubles `'`
24
- * and never emits a backslash, so it never depends on which way that setting points.
22
+ * no such rule. Use E'…' with doubled backslashes and quotes: PostgreSQL 18 lexical
23
+ * section 4.1.2.2 makes escape strings independent of standard_conforming_strings.
24
+ * Measured 2026-10-01: the former quote-only escaper allowed comment SQL injection when
25
+ * that setting was off; an input backslash escaped the first quote of a doubled pair.
25
26
  */
26
27
  import type { SqlError } from 'effect/unstable/sql/SqlError';
27
28
  import * as Effect from 'effect/Effect';
28
29
  import type { PostgresDatabaseAttributes, PostgresDatabaseProps } from './database-attrs.ts';
29
30
 
30
31
  /** What every function in this file needs from a client — real (`PgClient.PgClient`) or fake
31
- * (`fake-sql.ts`). Deliberately smaller than `SqlClient`: nothing here ever needs the tagged
32
- * template, transactions or streaming, because every statement is either bound params through
33
- * `.unsafe(text, params)` or a hand-escaped literal through `.unsafe(text)`. */
32
+ * (`fake-sql.ts`). Deliberately smaller than `SqlClient`: nothing here needs the tagged template
33
+ * or streaming. `transaction` is the exception `Postgres.Role`'s create and `Postgres.Grants`'
34
+ * repair share: role DDL commits per statement unless the statements share a transaction, and
35
+ * a grants repair's `REVOKE` must not autocommit apart from the `GRANT`s that restore it. */
34
36
  export interface PgExecutor {
35
37
  readonly unsafe: <A extends object>(
36
38
  sql: string,
37
39
  params?: ReadonlyArray<unknown>,
38
40
  ) => Effect.Effect<ReadonlyArray<A>, SqlError>;
41
+ /**
42
+ * Run `statements` in one transaction. `CREATE ROLE`, `ALTER ROLE` and `GRANT`/`REVOKE` of
43
+ * membership are transactional (`utility.c@REL_18_6`: those cases do not call
44
+ * `PreventInTransactionBlock`; `CREATE DATABASE` does). The socket client reserves one
45
+ * connection (`SqlClient.withTransaction`); the runner sends one `psql` script, because each
46
+ * `unsafe` call is its own session and would autocommit.
47
+ */
48
+ readonly transaction: (statements: readonly string[]) => Effect.Effect<void, SqlError>;
39
49
  }
40
50
 
41
51
  /** Single-token identifier quoting: wrap in `"`, double any embedded `"`. No dot-splitting —
@@ -43,9 +53,10 @@ export interface PgExecutor {
43
53
  * `NonReservedWord` too, for `OWNER` and `TABLESPACE`. */
44
54
  export const quoteIdent = (value: string): string => `"${value.replace(/"/g, '""')}"`;
45
55
 
46
- /** SQL string-literal quoting: wrap in `'`, double any embedded `'`. Used for every `WITH`
47
- * value that is locale or encoding text, never for an identifier (see the file header). */
48
- export const quoteStringLiteral = (value: string): string => `'${value.replace(/'/g, "''")}'`;
56
+ /** Escape-string quoting is safe with standard_conforming_strings either on or off.
57
+ * Double backslashes BEFORE quotes, so caller text cannot escape a closing delimiter. */
58
+ export const quoteStringLiteral = (value: string): string =>
59
+ `E'${value.replace(/\\/g, '\\\\').replace(/'/g, "''")}'`;
49
60
 
50
61
  const withOptions = (props: PostgresDatabaseProps, template?: string): string => {
51
62
  const parts: string[] = [`OWNER ${quoteIdent(props.owner)}`];
@@ -103,6 +114,22 @@ export const roleExists = (pg: PgExecutor, role: string): Effect.Effect<boolean,
103
114
  (rows) => rows.length > 0,
104
115
  );
105
116
 
117
+ const DATABASE_EXISTS_SQL = 'SELECT 1 AS present FROM pg_database WHERE datname = $1';
118
+
119
+ /**
120
+ * Does one database of the cluster exist? `Postgres.Schema` runs this over the FAMILY
121
+ * connection (no `withPg` database override) before `read` and `delete`, so a declared
122
+ * database that does not exist yet answers "schema absent" instead of the transports' untyped
123
+ * connect failures (`ConnectionError` over psql, `UnknownError` 3D000 over the socket). The
124
+ * probe targets the maintenance database the family connection already points at, which a
125
+ * cold plan can always reach.
126
+ */
127
+ export const databaseExists = (pg: PgExecutor, name: string): Effect.Effect<boolean, SqlError> =>
128
+ Effect.map(
129
+ pg.unsafe<{ readonly present: number }>(DATABASE_EXISTS_SQL, [name]),
130
+ (rows) => rows.length > 0,
131
+ );
132
+
106
133
  /** `datlocprovider` is Postgres's internal 1-byte `"char"` type, which this client decodes as
107
134
  * raw bytes rather than text (measured 2026-09-23 against the live socket — `docs/postgres.md`);
108
135
  * the `CASE` maps it to the same three words `pg_collation.h@REL_18_6#collprovider_name` returns