@supabase/pg-delta 1.0.0-alpha.4 → 1.0.0-alpha.6

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 (359) hide show
  1. package/README.md +40 -23
  2. package/dist/cli/app.js +26 -3
  3. package/dist/cli/bin/cli.js +5 -0
  4. package/dist/cli/commands/catalog-export.d.ts +5 -0
  5. package/dist/cli/commands/catalog-export.js +64 -0
  6. package/dist/cli/commands/declarative-apply.d.ts +6 -0
  7. package/dist/cli/commands/declarative-apply.js +288 -0
  8. package/dist/cli/commands/declarative-export.d.ts +5 -0
  9. package/dist/cli/commands/declarative-export.js +245 -0
  10. package/dist/cli/commands/plan.js +19 -6
  11. package/dist/cli/exit-code.d.ts +2 -0
  12. package/dist/cli/exit-code.js +7 -0
  13. package/dist/cli/formatters/tree/tree.js +3 -2
  14. package/dist/cli/utils/apply-display.d.ts +52 -0
  15. package/dist/cli/utils/apply-display.js +183 -0
  16. package/dist/cli/utils/export-display.d.ts +43 -0
  17. package/dist/cli/utils/export-display.js +202 -0
  18. package/dist/cli/utils/resolve-input.d.ts +7 -0
  19. package/dist/cli/utils/resolve-input.js +13 -0
  20. package/dist/core/catalog-export/index.d.ts +11 -0
  21. package/dist/core/catalog-export/index.js +10 -0
  22. package/dist/core/catalog.diff.d.ts +1 -0
  23. package/dist/core/catalog.diff.js +64 -48
  24. package/dist/core/catalog.model.d.ts +14 -1
  25. package/dist/core/catalog.model.js +103 -1
  26. package/dist/core/catalog.snapshot.d.ts +66 -0
  27. package/dist/core/catalog.snapshot.js +206 -0
  28. package/dist/core/declarative-apply/discover-sql.d.ts +18 -0
  29. package/dist/core/declarative-apply/discover-sql.js +86 -0
  30. package/dist/core/declarative-apply/extract-catalog-providers.d.ts +23 -0
  31. package/dist/core/declarative-apply/extract-catalog-providers.js +159 -0
  32. package/dist/core/declarative-apply/index.d.ts +49 -0
  33. package/dist/core/declarative-apply/index.js +134 -0
  34. package/dist/core/declarative-apply/round-apply.d.ts +100 -0
  35. package/dist/core/declarative-apply/round-apply.js +378 -0
  36. package/dist/core/export/file-mapper.d.ts +71 -0
  37. package/dist/core/export/file-mapper.js +474 -0
  38. package/dist/core/export/grouper.d.ts +13 -0
  39. package/dist/core/export/grouper.js +76 -0
  40. package/dist/core/export/index.d.ts +45 -0
  41. package/dist/core/export/index.js +63 -0
  42. package/dist/core/export/types.d.ts +84 -0
  43. package/dist/core/export/types.js +25 -0
  44. package/dist/core/fixtures/empty-catalogs/postgres-15-16-baseline.json +287 -0
  45. package/dist/core/integrations/filter/dsl.d.ts +38 -1
  46. package/dist/core/integrations/filter/dsl.js +20 -2
  47. package/dist/core/integrations/filter/extractors.js +42 -0
  48. package/dist/core/integrations/integration-dsl.d.ts +10 -0
  49. package/dist/core/integrations/supabase.d.ts +8 -0
  50. package/dist/core/integrations/supabase.js +9 -0
  51. package/dist/core/objects/aggregate/aggregate.diff.d.ts +2 -8
  52. package/dist/core/objects/aggregate/aggregate.diff.js +16 -70
  53. package/dist/core/objects/aggregate/aggregate.model.d.ts +8 -8
  54. package/dist/core/objects/aggregate/aggregate.model.js +1 -1
  55. package/dist/core/objects/aggregate/changes/aggregate.create.js +1 -1
  56. package/dist/core/objects/aggregate/changes/aggregate.drop.js +1 -1
  57. package/dist/core/objects/base.privilege-diff.d.ts +38 -13
  58. package/dist/core/objects/base.privilege-diff.js +104 -22
  59. package/dist/core/objects/base.privilege.d.ts +1 -0
  60. package/dist/core/objects/base.privilege.js +9 -2
  61. package/dist/core/objects/collation/collation.diff.d.ts +2 -3
  62. package/dist/core/objects/diff-context.d.ts +15 -0
  63. package/dist/core/objects/diff-context.js +1 -0
  64. package/dist/core/objects/domain/changes/domain.create.js +4 -2
  65. package/dist/core/objects/domain/domain.diff.d.ts +2 -8
  66. package/dist/core/objects/domain/domain.diff.js +16 -77
  67. package/dist/core/objects/domain/domain.model.js +1 -1
  68. package/dist/core/objects/event-trigger/event-trigger.diff.d.ts +2 -3
  69. package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.d.ts +2 -8
  70. package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.js +13 -77
  71. package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.js +2 -2
  72. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.d.ts +2 -8
  73. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.js +16 -77
  74. package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.js +1 -1
  75. package/dist/core/objects/foreign-data-wrapper/server/server.diff.d.ts +2 -8
  76. package/dist/core/objects/foreign-data-wrapper/server/server.diff.js +13 -77
  77. package/dist/core/objects/language/language.diff.d.ts +2 -5
  78. package/dist/core/objects/language/language.diff.js +7 -39
  79. package/dist/core/objects/materialized-view/materialized-view.diff.d.ts +2 -8
  80. package/dist/core/objects/materialized-view/materialized-view.diff.js +16 -158
  81. package/dist/core/objects/materialized-view/materialized-view.model.d.ts +3 -3
  82. package/dist/core/objects/materialized-view/materialized-view.model.js +1 -1
  83. package/dist/core/objects/procedure/changes/procedure.alter.js +12 -12
  84. package/dist/core/objects/procedure/procedure.diff.d.ts +2 -8
  85. package/dist/core/objects/procedure/procedure.diff.js +16 -77
  86. package/dist/core/objects/procedure/procedure.model.d.ts +9 -9
  87. package/dist/core/objects/procedure/procedure.model.js +1 -1
  88. package/dist/core/objects/publication/changes/publication.alter.d.ts +0 -9
  89. package/dist/core/objects/publication/changes/publication.alter.js +0 -14
  90. package/dist/core/objects/publication/changes/publication.types.d.ts +2 -2
  91. package/dist/core/objects/publication/publication.diff.d.ts +2 -3
  92. package/dist/core/objects/publication/publication.diff.js +8 -13
  93. package/dist/core/objects/rls-policy/changes/rls-policy.alter.js +3 -3
  94. package/dist/core/objects/rls-policy/rls-policy.model.d.ts +2 -2
  95. package/dist/core/objects/role/role.diff.js +22 -1
  96. package/dist/core/objects/role/role.model.d.ts +4 -3
  97. package/dist/core/objects/role/role.model.js +118 -12
  98. package/dist/core/objects/rule/rule.model.d.ts +1 -1
  99. package/dist/core/objects/schema/schema.diff.d.ts +2 -8
  100. package/dist/core/objects/schema/schema.diff.js +16 -77
  101. package/dist/core/objects/schema/schema.model.js +1 -1
  102. package/dist/core/objects/sequence/sequence.diff.d.ts +2 -8
  103. package/dist/core/objects/sequence/sequence.diff.js +16 -79
  104. package/dist/core/objects/sequence/sequence.model.js +1 -1
  105. package/dist/core/objects/subscription/subscription.diff.d.ts +2 -3
  106. package/dist/core/objects/table/changes/table.create.js +3 -0
  107. package/dist/core/objects/table/table.diff.d.ts +2 -8
  108. package/dist/core/objects/table/table.diff.js +26 -157
  109. package/dist/core/objects/table/table.model.d.ts +23 -22
  110. package/dist/core/objects/table/table.model.js +1 -1
  111. package/dist/core/objects/trigger/changes/trigger.create.js +2 -4
  112. package/dist/core/objects/trigger/trigger.model.d.ts +8 -0
  113. package/dist/core/objects/trigger/trigger.model.js +11 -0
  114. package/dist/core/objects/type/composite-type/composite-type.diff.d.ts +2 -8
  115. package/dist/core/objects/type/composite-type/composite-type.diff.js +16 -77
  116. package/dist/core/objects/type/composite-type/composite-type.model.d.ts +3 -3
  117. package/dist/core/objects/type/composite-type/composite-type.model.js +2 -1
  118. package/dist/core/objects/type/enum/enum.diff.d.ts +2 -8
  119. package/dist/core/objects/type/enum/enum.diff.js +25 -112
  120. package/dist/core/objects/type/enum/enum.model.js +1 -1
  121. package/dist/core/objects/type/range/changes/range.create.js +6 -3
  122. package/dist/core/objects/type/range/range.diff.d.ts +2 -8
  123. package/dist/core/objects/type/range/range.diff.js +16 -77
  124. package/dist/core/objects/type/range/range.model.js +1 -1
  125. package/dist/core/objects/view/view.diff.d.ts +2 -8
  126. package/dist/core/objects/view/view.diff.js +16 -158
  127. package/dist/core/objects/view/view.model.d.ts +18 -4
  128. package/dist/core/objects/view/view.model.js +3 -13
  129. package/dist/core/plan/apply.js +9 -26
  130. package/dist/core/plan/create.d.ts +19 -6
  131. package/dist/core/plan/create.js +134 -174
  132. package/dist/core/plan/serialize.js +16 -4
  133. package/dist/core/plan/sql-format/fixtures.js +3 -5
  134. package/dist/core/plan/sql-format/keyword-case.js +26 -1
  135. package/dist/core/plan/ssl-config.d.ts +32 -0
  136. package/dist/core/plan/ssl-config.js +115 -0
  137. package/dist/core/plan/types.d.ts +6 -0
  138. package/dist/core/postgres-config.d.ts +14 -0
  139. package/dist/core/postgres-config.js +53 -2
  140. package/dist/core/sort/graph-builder.js +10 -0
  141. package/dist/core/sort/logical-sort.js +31 -23
  142. package/dist/core/test-utils/assert-valid-sql.d.ts +10 -0
  143. package/dist/core/test-utils/assert-valid-sql.js +19 -0
  144. package/dist/index.d.ts +6 -0
  145. package/dist/index.js +6 -1
  146. package/package.json +21 -4
  147. package/src/cli/app.ts +27 -3
  148. package/src/cli/bin/cli.ts +6 -0
  149. package/src/cli/commands/catalog-export.ts +78 -0
  150. package/src/cli/commands/declarative-apply.diagnostics.test.ts +77 -0
  151. package/src/cli/commands/declarative-apply.ts +380 -0
  152. package/src/cli/commands/declarative-export.ts +330 -0
  153. package/src/cli/commands/plan.ts +28 -7
  154. package/src/cli/exit-code.test.ts +19 -0
  155. package/src/cli/exit-code.ts +7 -0
  156. package/src/cli/formatters/tree/tree.ts +3 -2
  157. package/src/cli/utils/apply-display.test.ts +348 -0
  158. package/src/cli/utils/apply-display.ts +238 -0
  159. package/src/cli/utils/export-display.test.ts +103 -0
  160. package/src/cli/utils/export-display.ts +275 -0
  161. package/src/cli/utils/integrations.test.ts +44 -0
  162. package/src/cli/utils/resolve-input.test.ts +38 -0
  163. package/src/cli/utils/resolve-input.ts +17 -0
  164. package/src/core/catalog-export/index.ts +20 -0
  165. package/src/core/catalog.diff.ts +79 -78
  166. package/src/core/catalog.model.test.ts +122 -0
  167. package/src/core/catalog.model.ts +127 -1
  168. package/src/core/catalog.snapshot.test.ts +464 -0
  169. package/src/core/catalog.snapshot.ts +289 -0
  170. package/src/core/declarative-apply/discover-sql.test.ts +103 -0
  171. package/src/core/declarative-apply/discover-sql.ts +107 -0
  172. package/src/core/declarative-apply/extract-catalog-providers.ts +220 -0
  173. package/src/core/declarative-apply/index.test.ts +67 -0
  174. package/src/core/declarative-apply/index.ts +205 -0
  175. package/src/core/declarative-apply/round-apply.test.ts +504 -0
  176. package/src/core/declarative-apply/round-apply.ts +562 -0
  177. package/src/core/expand-replace-dependencies.test.ts +70 -0
  178. package/src/core/export/file-mapper.test.ts +816 -0
  179. package/src/core/export/file-mapper.ts +574 -0
  180. package/src/core/export/grouper.ts +108 -0
  181. package/src/core/export/index.ts +129 -0
  182. package/src/core/export/types.ts +104 -0
  183. package/src/core/fixtures/empty-catalogs/postgres-15-16-baseline.json +287 -0
  184. package/src/core/integrations/filter/dsl.test.ts +211 -0
  185. package/src/core/integrations/filter/dsl.ts +65 -3
  186. package/src/core/integrations/filter/extractors.test.ts +244 -0
  187. package/src/core/integrations/filter/extractors.ts +42 -0
  188. package/src/core/integrations/integration-dsl.ts +10 -0
  189. package/src/core/integrations/serialize/dsl.test.ts +91 -0
  190. package/src/core/integrations/supabase.ts +9 -0
  191. package/src/core/objects/aggregate/aggregate.diff.ts +39 -95
  192. package/src/core/objects/aggregate/aggregate.model.ts +1 -1
  193. package/src/core/objects/aggregate/changes/aggregate.alter.test.ts +3 -1
  194. package/src/core/objects/aggregate/changes/aggregate.comment.test.ts +5 -2
  195. package/src/core/objects/aggregate/changes/aggregate.create.test.ts +6 -3
  196. package/src/core/objects/aggregate/changes/aggregate.create.ts +1 -1
  197. package/src/core/objects/aggregate/changes/aggregate.drop.test.ts +7 -3
  198. package/src/core/objects/aggregate/changes/aggregate.drop.ts +1 -1
  199. package/src/core/objects/aggregate/changes/aggregate.privilege.test.ts +9 -3
  200. package/src/core/objects/base.privilege-diff.ts +178 -30
  201. package/src/core/objects/base.privilege.ts +9 -2
  202. package/src/core/objects/collation/changes/collation.alter.test.ts +7 -2
  203. package/src/core/objects/collation/changes/collation.create.test.ts +7 -2
  204. package/src/core/objects/collation/changes/collation.drop.test.ts +4 -1
  205. package/src/core/objects/collation/collation.diff.test.ts +9 -12
  206. package/src/core/objects/collation/collation.diff.ts +2 -1
  207. package/src/core/objects/diff-context.ts +16 -0
  208. package/src/core/objects/domain/changes/domain.alter.test.ts +28 -9
  209. package/src/core/objects/domain/changes/domain.create.test.ts +32 -2
  210. package/src/core/objects/domain/changes/domain.create.ts +7 -1
  211. package/src/core/objects/domain/changes/domain.drop.test.ts +4 -1
  212. package/src/core/objects/domain/domain.diff.ts +39 -102
  213. package/src/core/objects/domain/domain.model.ts +1 -1
  214. package/src/core/objects/event-trigger/changes/event-trigger.alter.test.ts +10 -3
  215. package/src/core/objects/event-trigger/changes/event-trigger.create.test.ts +4 -1
  216. package/src/core/objects/event-trigger/changes/event-trigger.drop.test.ts +4 -1
  217. package/src/core/objects/event-trigger/event-trigger.diff.test.ts +12 -7
  218. package/src/core/objects/event-trigger/event-trigger.diff.ts +2 -1
  219. package/src/core/objects/extension/changes/extension.alter.test.ts +7 -2
  220. package/src/core/objects/extension/changes/extension.create.test.ts +4 -1
  221. package/src/core/objects/extension/changes/extension.drop.test.ts +4 -1
  222. package/src/core/objects/extension/extension.model.test.ts +98 -0
  223. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.alter.test.ts +16 -5
  224. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.create.test.ts +51 -16
  225. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.drop.test.ts +4 -1
  226. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.test.ts +111 -4
  227. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.ts +31 -101
  228. package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.ts +2 -2
  229. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.alter.test.ts +46 -15
  230. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.create.test.ts +13 -4
  231. package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.drop.test.ts +4 -1
  232. package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.ts +39 -102
  233. package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.ts +1 -1
  234. package/src/core/objects/foreign-data-wrapper/server/changes/server.alter.test.ts +22 -7
  235. package/src/core/objects/foreign-data-wrapper/server/changes/server.create.test.ts +19 -6
  236. package/src/core/objects/foreign-data-wrapper/server/changes/server.drop.test.ts +4 -1
  237. package/src/core/objects/foreign-data-wrapper/server/server.diff.test.ts +95 -0
  238. package/src/core/objects/foreign-data-wrapper/server/server.diff.ts +31 -101
  239. package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.alter.test.ts +13 -4
  240. package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.create.test.ts +16 -5
  241. package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.drop.test.ts +10 -3
  242. package/src/core/objects/index/changes/index.alter.test.ts +13 -4
  243. package/src/core/objects/index/changes/index.create.test.ts +4 -1
  244. package/src/core/objects/index/changes/index.drop.test.ts +4 -1
  245. package/src/core/objects/language/changes/language.alter.test.ts +4 -1
  246. package/src/core/objects/language/changes/language.create.test.ts +4 -1
  247. package/src/core/objects/language/changes/language.drop.test.ts +4 -1
  248. package/src/core/objects/language/language.diff.test.ts +86 -4
  249. package/src/core/objects/language/language.diff.ts +17 -49
  250. package/src/core/objects/materialized-view/changes/materialized-view.alter.test.ts +10 -3
  251. package/src/core/objects/materialized-view/changes/materialized-view.create.test.ts +7 -2
  252. package/src/core/objects/materialized-view/changes/materialized-view.drop.test.ts +4 -1
  253. package/src/core/objects/materialized-view/materialized-view.diff.test.ts +162 -0
  254. package/src/core/objects/materialized-view/materialized-view.diff.ts +41 -191
  255. package/src/core/objects/materialized-view/materialized-view.model.ts +1 -1
  256. package/src/core/objects/procedure/changes/procedure.alter.test.ts +121 -49
  257. package/src/core/objects/procedure/changes/procedure.alter.ts +15 -12
  258. package/src/core/objects/procedure/changes/procedure.create.test.ts +4 -1
  259. package/src/core/objects/procedure/changes/procedure.drop.test.ts +7 -2
  260. package/src/core/objects/procedure/procedure.diff.ts +39 -102
  261. package/src/core/objects/procedure/procedure.model.ts +1 -1
  262. package/src/core/objects/publication/changes/publication.alter.test.ts +15 -21
  263. package/src/core/objects/publication/changes/publication.alter.ts +0 -18
  264. package/src/core/objects/publication/changes/publication.comment.test.ts +5 -2
  265. package/src/core/objects/publication/changes/publication.create.test.ts +5 -2
  266. package/src/core/objects/publication/changes/publication.drop.test.ts +3 -1
  267. package/src/core/objects/publication/changes/publication.types.ts +0 -2
  268. package/src/core/objects/publication/publication.diff.test.ts +24 -19
  269. package/src/core/objects/publication/publication.diff.ts +9 -15
  270. package/src/core/objects/rls-policy/changes/rls-policy.alter.test.ts +31 -14
  271. package/src/core/objects/rls-policy/changes/rls-policy.alter.ts +3 -3
  272. package/src/core/objects/rls-policy/changes/rls-policy.create.test.ts +10 -3
  273. package/src/core/objects/rls-policy/changes/rls-policy.drop.test.ts +4 -1
  274. package/src/core/objects/role/changes/role.alter.test.ts +31 -15
  275. package/src/core/objects/role/changes/role.create.test.ts +6 -2
  276. package/src/core/objects/role/changes/role.drop.test.ts +4 -1
  277. package/src/core/objects/role/role.diff.test.ts +235 -0
  278. package/src/core/objects/role/role.diff.ts +21 -1
  279. package/src/core/objects/role/role.model.ts +122 -14
  280. package/src/core/objects/rule/changes/rule.alter.test.ts +7 -3
  281. package/src/core/objects/rule/changes/rule.comment.test.ts +5 -2
  282. package/src/core/objects/rule/changes/rule.create.test.ts +6 -2
  283. package/src/core/objects/rule/changes/rule.drop.test.ts +3 -1
  284. package/src/core/objects/schema/changes/schema.alter.test.ts +4 -1
  285. package/src/core/objects/schema/changes/schema.create.test.ts +4 -1
  286. package/src/core/objects/schema/changes/schema.drop.test.ts +4 -1
  287. package/src/core/objects/schema/schema.diff.ts +39 -102
  288. package/src/core/objects/schema/schema.model.ts +1 -1
  289. package/src/core/objects/sequence/changes/sequence.alter.test.ts +11 -5
  290. package/src/core/objects/sequence/changes/sequence.create.test.ts +8 -3
  291. package/src/core/objects/sequence/changes/sequence.drop.test.ts +4 -1
  292. package/src/core/objects/sequence/sequence.diff.test.ts +114 -0
  293. package/src/core/objects/sequence/sequence.diff.ts +39 -104
  294. package/src/core/objects/sequence/sequence.model.ts +1 -1
  295. package/src/core/objects/subscription/changes/subscription.alter.test.ts +15 -5
  296. package/src/core/objects/subscription/changes/subscription.comment.test.ts +5 -2
  297. package/src/core/objects/subscription/changes/subscription.create.test.ts +5 -2
  298. package/src/core/objects/subscription/changes/subscription.drop.test.ts +3 -1
  299. package/src/core/objects/subscription/subscription.diff.test.ts +16 -11
  300. package/src/core/objects/subscription/subscription.diff.ts +2 -1
  301. package/src/core/objects/table/changes/table.alter.test.ts +38 -15
  302. package/src/core/objects/table/changes/table.create.test.ts +41 -3
  303. package/src/core/objects/table/changes/table.create.ts +4 -0
  304. package/src/core/objects/table/changes/table.drop.test.ts +3 -1
  305. package/src/core/objects/table/table.diff.test.ts +157 -0
  306. package/src/core/objects/table/table.diff.ts +54 -190
  307. package/src/core/objects/table/table.model.ts +1 -1
  308. package/src/core/objects/trigger/changes/trigger.alter.test.ts +8 -4
  309. package/src/core/objects/trigger/changes/trigger.create.test.ts +5 -1
  310. package/src/core/objects/trigger/changes/trigger.create.ts +7 -4
  311. package/src/core/objects/trigger/changes/trigger.drop.test.ts +5 -1
  312. package/src/core/objects/trigger/trigger.diff.test.ts +1 -0
  313. package/src/core/objects/trigger/trigger.model.ts +12 -0
  314. package/src/core/objects/type/composite-type/changes/composite-type.alter.test.ts +10 -4
  315. package/src/core/objects/type/composite-type/changes/composite-type.create.test.ts +7 -2
  316. package/src/core/objects/type/composite-type/changes/composite-type.drop.test.ts +4 -1
  317. package/src/core/objects/type/composite-type/composite-type.diff.test.ts +78 -0
  318. package/src/core/objects/type/composite-type/composite-type.diff.ts +39 -101
  319. package/src/core/objects/type/composite-type/composite-type.model.ts +2 -1
  320. package/src/core/objects/type/enum/changes/enum.alter.test.ts +14 -5
  321. package/src/core/objects/type/enum/changes/enum.create.test.ts +4 -1
  322. package/src/core/objects/type/enum/changes/enum.drop.test.ts +4 -1
  323. package/src/core/objects/type/enum/enum.diff.test.ts +181 -0
  324. package/src/core/objects/type/enum/enum.diff.ts +58 -146
  325. package/src/core/objects/type/enum/enum.model.ts +1 -1
  326. package/src/core/objects/type/range/changes/range.alter.test.ts +3 -1
  327. package/src/core/objects/type/range/changes/range.create.test.ts +5 -2
  328. package/src/core/objects/type/range/changes/range.create.ts +6 -2
  329. package/src/core/objects/type/range/changes/range.drop.test.ts +3 -1
  330. package/src/core/objects/type/range/range.diff.test.ts +77 -0
  331. package/src/core/objects/type/range/range.diff.ts +39 -101
  332. package/src/core/objects/type/range/range.model.ts +1 -1
  333. package/src/core/objects/view/changes/view.alter.test.ts +8 -3
  334. package/src/core/objects/view/changes/view.create.test.ts +7 -2
  335. package/src/core/objects/view/changes/view.drop.test.ts +4 -1
  336. package/src/core/objects/view/view.diff.test.ts +82 -0
  337. package/src/core/objects/view/view.diff.ts +41 -191
  338. package/src/core/objects/view/view.model.ts +3 -17
  339. package/src/core/plan/apply.ts +9 -27
  340. package/src/core/plan/create.ts +173 -237
  341. package/src/core/plan/serialize.test.ts +317 -0
  342. package/src/core/plan/serialize.ts +18 -4
  343. package/src/core/plan/sql-format/fixtures.ts +2 -5
  344. package/src/core/plan/sql-format/format-lowercase-coverage.test.ts +52 -0
  345. package/src/core/plan/sql-format/format-off.test.ts +14 -17
  346. package/src/core/plan/sql-format/format-pretty-lower-leading.test.ts +27 -22
  347. package/src/core/plan/sql-format/format-pretty-narrow.test.ts +17 -21
  348. package/src/core/plan/sql-format/format-pretty-preserve.test.ts +25 -20
  349. package/src/core/plan/sql-format/format-pretty-upper.test.ts +23 -20
  350. package/src/core/plan/sql-format/keyword-case.ts +36 -1
  351. package/src/core/plan/ssl-config.ts +172 -0
  352. package/src/core/plan/types.ts +6 -0
  353. package/src/core/postgres-config.ts +71 -2
  354. package/src/core/sort/graph-builder.ts +12 -0
  355. package/src/core/sort/logical-sort.test.ts +371 -0
  356. package/src/core/sort/logical-sort.ts +32 -25
  357. package/src/core/sort/topological-sort.test.ts +275 -0
  358. package/src/core/test-utils/assert-valid-sql.ts +20 -0
  359. package/src/index.ts +26 -2
@@ -494,10 +494,6 @@ describe("sql formatting snapshots", () => {
494
494
  'insert, update, delete, truncate',
495
495
  publish_via_partition_root = false);
496
496
 
497
- -- publication.alter.set_all_tables
498
- ALTER PUBLICATION pub_custom
499
- SET FOR ALL TABLES;
500
-
501
497
  -- publication.alter.set_list
502
498
  ALTER PUBLICATION pub_custom
503
499
  SET TABLE
@@ -636,31 +632,31 @@ describe("sql formatting snapshots", () => {
636
632
 
637
633
  -- function.alter.change_owner
638
634
  ALTER FUNCTION
639
- public.calculate_metrics_for_analytics_dashboard_with_extended_name OWNER TO new_admin;
635
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) OWNER TO new_admin;
640
636
 
641
637
  -- function.alter.set_security
642
638
  ALTER FUNCTION
643
- public.calculate_metrics_for_analytics_dashboard_with_extended_name SECURITY INVOKER;
639
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) SECURITY INVOKER;
644
640
 
645
641
  -- function.alter.set_config
646
642
  ALTER FUNCTION
647
- public.calculate_metrics_for_analytics_dashboard_with_extended_name SET work_mem TO '256MB';
643
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) SET work_mem TO '256MB';
648
644
 
649
645
  -- function.alter.set_volatility
650
646
  ALTER FUNCTION
651
- public.calculate_metrics_for_analytics_dashboard_with_extended_name IMMUTABLE;
647
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) IMMUTABLE;
652
648
 
653
649
  -- function.alter.set_strictness
654
650
  ALTER FUNCTION
655
- public.calculate_metrics_for_analytics_dashboard_with_extended_name CALLED ON NULL INPUT;
651
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) CALLED ON NULL INPUT;
656
652
 
657
653
  -- function.alter.set_leakproof
658
654
  ALTER FUNCTION
659
- public.calculate_metrics_for_analytics_dashboard_with_extended_name LEAKPROOF;
655
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) LEAKPROOF;
660
656
 
661
657
  -- function.alter.set_parallel
662
658
  ALTER FUNCTION
663
- public.calculate_metrics_for_analytics_dashboard_with_extended_name PARALLEL RESTRICTED;
659
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) PARALLEL RESTRICTED;
664
660
 
665
661
  -- function.comment
666
662
  COMMENT ON FUNCTION
@@ -746,17 +742,17 @@ describe("sql formatting snapshots", () => {
746
742
  public.table_with_very_long_name_for_formatting_and_wrapping_test;
747
743
 
748
744
  -- policy.alter.set_roles
749
- ALTER POLICY public.allow_select_own
745
+ ALTER POLICY allow_select_own
750
746
  ON
751
747
  public.table_with_very_long_name_for_formatting_and_wrapping_test TO authenticated, anon;
752
748
 
753
749
  -- policy.alter.set_using
754
- ALTER POLICY public.allow_select_own
750
+ ALTER POLICY allow_select_own
755
751
  ON
756
752
  public.table_with_very_long_name_for_formatting_and_wrapping_test USING (auth.uid() = user_id AND status = 'active');
757
753
 
758
754
  -- policy.alter.set_with_check
759
- ALTER POLICY public.allow_select_own
755
+ ALTER POLICY allow_select_own
760
756
  ON
761
757
  public.table_with_very_long_name_for_formatting_and_wrapping_test WITH CHECK (auth.uid() = user_id);
762
758
 
@@ -906,7 +902,7 @@ describe("sql formatting snapshots", () => {
906
902
  STYPE = anycompatiblearray,
907
903
  COMBINEFUNC = array_cat,
908
904
  INITCOND = '{}',
909
- PARALLEL SAFE,
905
+ PARALLEL = SAFE,
910
906
  STRICT
911
907
  );
912
908
 
@@ -1201,18 +1197,18 @@ describe("sql formatting snapshots", () => {
1201
1197
  NULL;
1202
1198
 
1203
1199
  -- foreign_table.grant
1204
- GRANT SELECT ON
1205
- FOREIGN TABLE public.remote_users TO
1200
+ GRANT SELECT
1201
+ ON TABLE public.remote_users TO
1206
1202
  app_reader;
1207
1203
 
1208
1204
  -- foreign_table.revoke
1209
- REVOKE SELECT ON
1210
- FOREIGN TABLE public.remote_users FROM
1205
+ REVOKE SELECT
1206
+ ON TABLE public.remote_users FROM
1211
1207
  app_reader;
1212
1208
 
1213
1209
  -- foreign_table.revoke_grant_option
1214
- REVOKE GRANT OPTION FOR SELECT ON
1215
- FOREIGN TABLE public.remote_users FROM
1210
+ REVOKE GRANT OPTION FOR SELECT
1211
+ ON TABLE public.remote_users FROM
1216
1212
  app_reader;
1217
1213
 
1218
1214
  -- server.create
@@ -401,9 +401,6 @@ describe("sql formatting snapshots", () => {
401
401
  ALTER PUBLICATION pub_custom
402
402
  SET (publish = 'insert, update, delete, truncate', publish_via_partition_root = false);
403
403
 
404
- -- publication.alter.set_all_tables
405
- ALTER PUBLICATION pub_custom SET FOR ALL TABLES;
406
-
407
404
  -- publication.alter.set_list
408
405
  ALTER PUBLICATION pub_custom
409
406
  SET TABLE
@@ -518,28 +515,38 @@ describe("sql formatting snapshots", () => {
518
515
  IN "p_table_name_for_metrics" text, IN "p_limit_count_default" integer);
519
516
 
520
517
  -- function.alter.change_owner
521
- ALTER FUNCTION public.calculate_metrics_for_analytics_dashboard_with_extended_name OWNER TO
518
+ ALTER FUNCTION
519
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) OWNER TO
522
520
  new_admin;
523
521
 
524
522
  -- function.alter.set_security
525
- ALTER FUNCTION public.calculate_metrics_for_analytics_dashboard_with_extended_name SECURITY INVOKER;
523
+ ALTER FUNCTION
524
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) SECURITY
525
+ INVOKER;
526
526
 
527
527
  -- function.alter.set_config
528
- ALTER FUNCTION public.calculate_metrics_for_analytics_dashboard_with_extended_name
528
+ ALTER FUNCTION
529
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer)
529
530
  SET work_mem TO '256MB';
530
531
 
531
532
  -- function.alter.set_volatility
532
- ALTER FUNCTION public.calculate_metrics_for_analytics_dashboard_with_extended_name IMMUTABLE;
533
+ ALTER FUNCTION
534
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer)
535
+ IMMUTABLE;
533
536
 
534
537
  -- function.alter.set_strictness
535
- ALTER FUNCTION public.calculate_metrics_for_analytics_dashboard_with_extended_name CALLED
538
+ ALTER FUNCTION
539
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) CALLED
536
540
  ON NULL INPUT;
537
541
 
538
542
  -- function.alter.set_leakproof
539
- ALTER FUNCTION public.calculate_metrics_for_analytics_dashboard_with_extended_name LEAKPROOF;
543
+ ALTER FUNCTION
544
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer)
545
+ LEAKPROOF;
540
546
 
541
547
  -- function.alter.set_parallel
542
- ALTER FUNCTION public.calculate_metrics_for_analytics_dashboard_with_extended_name PARALLEL
548
+ ALTER FUNCTION
549
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) PARALLEL
543
550
  RESTRICTED;
544
551
 
545
552
  -- function.comment
@@ -620,18 +627,16 @@ describe("sql formatting snapshots", () => {
620
627
  DROP POLICY allow_select_own ON public.table_with_very_long_name_for_formatting_and_wrapping_test;
621
628
 
622
629
  -- policy.alter.set_roles
623
- ALTER POLICY public.allow_select_own
630
+ ALTER POLICY allow_select_own
624
631
  ON public.table_with_very_long_name_for_formatting_and_wrapping_test TO authenticated, anon;
625
632
 
626
633
  -- policy.alter.set_using
627
- ALTER POLICY public.allow_select_own
628
- ON public.table_with_very_long_name_for_formatting_and_wrapping_test
634
+ ALTER POLICY allow_select_own ON public.table_with_very_long_name_for_formatting_and_wrapping_test
629
635
  USING (auth.uid() = user_id AND status = 'active');
630
636
 
631
637
  -- policy.alter.set_with_check
632
- ALTER POLICY public.allow_select_own
633
- ON public.table_with_very_long_name_for_formatting_and_wrapping_test WITH
634
- CHECK (auth.uid() = user_id);
638
+ ALTER POLICY allow_select_own ON public.table_with_very_long_name_for_formatting_and_wrapping_test
639
+ WITH CHECK (auth.uid() = user_id);
635
640
 
636
641
  -- policy.comment
637
642
  COMMENT ON POLICY allow_select_own
@@ -741,7 +746,7 @@ describe("sql formatting snapshots", () => {
741
746
  STYPE = anycompatiblearray,
742
747
  COMBINEFUNC = array_cat,
743
748
  INITCOND = '{}',
744
- PARALLEL SAFE,
749
+ PARALLEL = SAFE,
745
750
  STRICT
746
751
  );
747
752
 
@@ -986,13 +991,13 @@ describe("sql formatting snapshots", () => {
986
991
  COMMENT ON FOREIGN TABLE public.remote_users IS NULL;
987
992
 
988
993
  -- foreign_table.grant
989
- GRANT SELECT ON FOREIGN TABLE public.remote_users TO app_reader;
994
+ GRANT SELECT ON TABLE public.remote_users TO app_reader;
990
995
 
991
996
  -- foreign_table.revoke
992
- REVOKE SELECT ON FOREIGN TABLE public.remote_users FROM app_reader;
997
+ REVOKE SELECT ON TABLE public.remote_users FROM app_reader;
993
998
 
994
999
  -- foreign_table.revoke_grant_option
995
- REVOKE GRANT OPTION FOR SELECT ON FOREIGN TABLE public.remote_users FROM app_reader;
1000
+ REVOKE GRANT OPTION FOR SELECT ON TABLE public.remote_users FROM app_reader;
996
1001
 
997
1002
  -- server.create
998
1003
  CREATE SERVER remote_server
@@ -396,9 +396,6 @@ describe("sql formatting snapshots", () => {
396
396
  ALTER PUBLICATION pub_custom
397
397
  SET (publish = 'insert, update, delete, truncate', publish_via_partition_root = false);
398
398
 
399
- -- publication.alter.set_all_tables
400
- ALTER PUBLICATION pub_custom SET FOR ALL TABLES;
401
-
402
399
  -- publication.alter.set_list
403
400
  ALTER PUBLICATION pub_custom
404
401
  SET TABLE
@@ -513,28 +510,36 @@ describe("sql formatting snapshots", () => {
513
510
  IN "p_table_name_for_metrics" text, IN "p_limit_count_default" integer);
514
511
 
515
512
  -- function.alter.change_owner
516
- ALTER FUNCTION public.calculate_metrics_for_analytics_dashboard_with_extended_name OWNER TO
513
+ ALTER FUNCTION
514
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) OWNER TO
517
515
  new_admin;
518
516
 
519
517
  -- function.alter.set_security
520
- ALTER FUNCTION public.calculate_metrics_for_analytics_dashboard_with_extended_name SECURITY INVOKER;
518
+ ALTER FUNCTION
519
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) SECURITY
520
+ INVOKER;
521
521
 
522
522
  -- function.alter.set_config
523
- ALTER FUNCTION public.calculate_metrics_for_analytics_dashboard_with_extended_name
523
+ ALTER FUNCTION
524
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer)
524
525
  SET work_mem TO '256MB';
525
526
 
526
527
  -- function.alter.set_volatility
527
- ALTER FUNCTION public.calculate_metrics_for_analytics_dashboard_with_extended_name IMMUTABLE;
528
+ ALTER FUNCTION
529
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) IMMUTABLE;
528
530
 
529
531
  -- function.alter.set_strictness
530
- ALTER FUNCTION public.calculate_metrics_for_analytics_dashboard_with_extended_name CALLED
532
+ ALTER FUNCTION
533
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) CALLED
531
534
  ON NULL INPUT;
532
535
 
533
536
  -- function.alter.set_leakproof
534
- ALTER FUNCTION public.calculate_metrics_for_analytics_dashboard_with_extended_name LEAKPROOF;
537
+ ALTER FUNCTION
538
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) LEAKPROOF;
535
539
 
536
540
  -- function.alter.set_parallel
537
- ALTER FUNCTION public.calculate_metrics_for_analytics_dashboard_with_extended_name PARALLEL
541
+ ALTER FUNCTION
542
+ public.calculate_metrics_for_analytics_dashboard_with_extended_name(text, text, integer) PARALLEL
538
543
  RESTRICTED;
539
544
 
540
545
  -- function.comment
@@ -613,18 +618,16 @@ describe("sql formatting snapshots", () => {
613
618
  DROP POLICY allow_select_own ON public.table_with_very_long_name_for_formatting_and_wrapping_test;
614
619
 
615
620
  -- policy.alter.set_roles
616
- ALTER POLICY public.allow_select_own
621
+ ALTER POLICY allow_select_own
617
622
  ON public.table_with_very_long_name_for_formatting_and_wrapping_test TO authenticated, anon;
618
623
 
619
624
  -- policy.alter.set_using
620
- ALTER POLICY public.allow_select_own
621
- ON public.table_with_very_long_name_for_formatting_and_wrapping_test
625
+ ALTER POLICY allow_select_own ON public.table_with_very_long_name_for_formatting_and_wrapping_test
622
626
  USING (auth.uid() = user_id AND status = 'active');
623
627
 
624
628
  -- policy.alter.set_with_check
625
- ALTER POLICY public.allow_select_own
626
- ON public.table_with_very_long_name_for_formatting_and_wrapping_test WITH
627
- CHECK (auth.uid() = user_id);
629
+ ALTER POLICY allow_select_own ON public.table_with_very_long_name_for_formatting_and_wrapping_test
630
+ WITH CHECK (auth.uid() = user_id);
628
631
 
629
632
  -- policy.comment
630
633
  COMMENT ON POLICY allow_select_own
@@ -734,7 +737,7 @@ describe("sql formatting snapshots", () => {
734
737
  STYPE = anycompatiblearray,
735
738
  COMBINEFUNC = array_cat,
736
739
  INITCOND = '{}',
737
- PARALLEL SAFE,
740
+ PARALLEL = SAFE,
738
741
  STRICT
739
742
  );
740
743
 
@@ -979,13 +982,13 @@ describe("sql formatting snapshots", () => {
979
982
  COMMENT ON FOREIGN TABLE public.remote_users IS NULL;
980
983
 
981
984
  -- foreign_table.grant
982
- GRANT SELECT ON FOREIGN TABLE public.remote_users TO app_reader;
985
+ GRANT SELECT ON TABLE public.remote_users TO app_reader;
983
986
 
984
987
  -- foreign_table.revoke
985
- REVOKE SELECT ON FOREIGN TABLE public.remote_users FROM app_reader;
988
+ REVOKE SELECT ON TABLE public.remote_users FROM app_reader;
986
989
 
987
990
  -- foreign_table.revoke_grant_option
988
- REVOKE GRANT OPTION FOR SELECT ON FOREIGN TABLE public.remote_users FROM app_reader;
991
+ REVOKE GRANT OPTION FOR SELECT ON TABLE public.remote_users FROM app_reader;
989
992
 
990
993
  -- server.create
991
994
  CREATE SERVER remote_server
@@ -207,6 +207,36 @@ const STRUCTURAL_TOP_LEVEL_KEYWORDS = new Set([
207
207
  "WRAPPER",
208
208
  "MAPPING",
209
209
  ]);
210
+ const EMPTY_SCOPED_SET: ReadonlySet<string> = new Set();
211
+
212
+ const ALTER_DEFAULT_PRIVILEGES_KEYWORDS: ReadonlySet<string> = new Set([
213
+ "PUBLIC",
214
+ "SEQUENCES",
215
+ "ROUTINES",
216
+ "TYPES",
217
+ "SCHEMAS",
218
+ ]);
219
+
220
+ const GRANT_REVOKE_KEYWORDS: ReadonlySet<string> = new Set(["PUBLIC"]);
221
+
222
+ function getStatementScopedKeywords(
223
+ topLevelTokens: Array<{ token: Token; index: number }>,
224
+ ): ReadonlySet<string> {
225
+ const first = topLevelTokens[0]?.token.upper;
226
+ const second = topLevelTokens[1]?.token.upper;
227
+ const third = topLevelTokens[2]?.token.upper;
228
+
229
+ if (first === "ALTER" && second === "DEFAULT" && third === "PRIVILEGES") {
230
+ return ALTER_DEFAULT_PRIVILEGES_KEYWORDS;
231
+ }
232
+
233
+ if (first === "GRANT" || first === "REVOKE") {
234
+ return GRANT_REVOKE_KEYWORDS;
235
+ }
236
+
237
+ return EMPTY_SCOPED_SET;
238
+ }
239
+
210
240
  const ALTER_TYPE_BOUNDARY_KEYWORDS = new Set([
211
241
  "COLLATE",
212
242
  "USING",
@@ -287,6 +317,7 @@ function collectCaseableTokenStarts(
287
317
  if (topLevelTokens.length === 0) return caseable;
288
318
 
289
319
  const command = topLevelTokens[0].token.upper;
320
+ const scopedKeywords = getStatementScopedKeywords(topLevelTokens);
290
321
  const objectNameTokenIndexes = new Set<number>();
291
322
  for (let topIndex = 0; topIndex < topLevelTokens.length; topIndex += 1) {
292
323
  if (isLikelyObjectNameToken(command, topLevelTokens, topIndex)) {
@@ -297,7 +328,8 @@ function collectCaseableTokenStarts(
297
328
  for (let index = 0; index < tokens.length; index += 1) {
298
329
  const token = tokens[index];
299
330
  const upper = token.upper;
300
- if (!STRUCTURAL_TOP_LEVEL_KEYWORDS.has(upper)) continue;
331
+ if (!STRUCTURAL_TOP_LEVEL_KEYWORDS.has(upper) && !scopedKeywords.has(upper))
332
+ continue;
301
333
  if (objectNameTokenIndexes.has(index)) continue;
302
334
  if (isQualifiedIdentifierToken(statement, token)) continue;
303
335
 
@@ -436,6 +468,9 @@ function isCaseableInContext(
436
468
  if (upper === "IDENTITY") {
437
469
  return prev === "REPLICA" || prev === "AS";
438
470
  }
471
+ if (upper === "PUBLIC") {
472
+ return prev === "TO" || prev === "FROM";
473
+ }
439
474
  if (upper === "OR") {
440
475
  return true;
441
476
  }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * SSL configuration parsing for PostgreSQL connection URLs.
3
+ *
4
+ * Supports sslmode and certificate paths (URL params or env). Used by plan,
5
+ * apply, and catalog-export when connecting to source/target databases.
6
+ */
7
+
8
+ import { readFile } from "node:fs/promises";
9
+
10
+ /** Parsed SSL options for the pg client plus URL with SSL params stripped (internal). */
11
+ type SslConfig = {
12
+ ssl?:
13
+ | boolean
14
+ | {
15
+ rejectUnauthorized: boolean;
16
+ ca?: string;
17
+ cert?: string;
18
+ key?: string;
19
+ /**
20
+ * Custom server identity check function.
21
+ * Used to skip hostname verification for verify-ca mode.
22
+ * Returns undefined to indicate success (no error).
23
+ */
24
+ checkServerIdentity?: () => undefined;
25
+ };
26
+ cleanedUrl: string;
27
+ };
28
+
29
+ /**
30
+ * Parse SSL configuration from a PostgreSQL connection URL.
31
+ * Supports sslmode (require, verify-ca, verify-full, prefer, disable).
32
+ * Certificates can be provided via:
33
+ * - Query string parameters (file paths): sslrootcert, sslcert, sslkey (preferred)
34
+ * - Environment variables (content): PGDELTA_SOURCE_SSLROOTCERT/SSLCERT/SSLKEY or PGDELTA_TARGET_SSLROOTCERT/SSLCERT/SSLKEY
35
+ * Returns SSL options for the postgres.js library and a cleaned URL without SSL-related query parameters.
36
+ */
37
+ export async function parseSslConfig(
38
+ url: string,
39
+ connectionType: "source" | "target",
40
+ ): Promise<SslConfig> {
41
+ const urlObj = new URL(url);
42
+ const sslmode = urlObj.searchParams.get("sslmode");
43
+ const sslrootcert = urlObj.searchParams.get("sslrootcert");
44
+ const sslcert = urlObj.searchParams.get("sslcert");
45
+ const sslkey = urlObj.searchParams.get("sslkey");
46
+
47
+ // Remove SSL-related query parameters since we parse them ourselves
48
+ urlObj.searchParams.delete("sslmode");
49
+ urlObj.searchParams.delete("sslrootcert");
50
+ urlObj.searchParams.delete("sslcert");
51
+ urlObj.searchParams.delete("sslkey");
52
+ const cleanedUrl = urlObj.toString();
53
+
54
+ // Handle different SSL modes
55
+ if (sslmode === "disable") {
56
+ return { cleanedUrl, ssl: false };
57
+ }
58
+
59
+ if (
60
+ sslmode === "require" ||
61
+ sslmode === "prefer" ||
62
+ sslmode === "verify-ca" ||
63
+ sslmode === "verify-full"
64
+ ) {
65
+ // Helper function to get certificate value: query param (file path) takes precedence over env var (content)
66
+ const getCertValue = async (
67
+ queryParam: string | null,
68
+ envVarName: string,
69
+ ): Promise<string | undefined> => {
70
+ // Prefer query parameter (file path)
71
+ if (queryParam) {
72
+ try {
73
+ return await readFile(queryParam, "utf-8");
74
+ } catch (error) {
75
+ throw new Error(
76
+ `Failed to read certificate file '${queryParam}': ${error instanceof Error ? error.message : String(error)}`,
77
+ );
78
+ }
79
+ }
80
+ // Fallback to environment variable (content)
81
+ const envValue = process.env[envVarName];
82
+ return envValue || undefined;
83
+ };
84
+
85
+ const hasExplicitVerification =
86
+ sslmode === "verify-ca" || sslmode === "verify-full";
87
+
88
+ // Get CA certificate value.
89
+ // - verify-ca/verify-full: check query param first, then env var
90
+ // - require/prefer: only check query param (libpq backward compatibility
91
+ // requires an explicit root CA *file*, not a global env var)
92
+ const caEnvVar =
93
+ connectionType === "source"
94
+ ? "PGDELTA_SOURCE_SSLROOTCERT"
95
+ : "PGDELTA_TARGET_SSLROOTCERT";
96
+ let caValue: string | undefined;
97
+ if (sslrootcert) {
98
+ // Explicit file path in query param — always honour it
99
+ caValue = await getCertValue(sslrootcert, caEnvVar);
100
+ } else if (hasExplicitVerification) {
101
+ // verify-ca / verify-full without file path — fall back to env var
102
+ caValue = await getCertValue(null, caEnvVar);
103
+ }
104
+ // require/prefer without sslrootcert: no CA cert, no verification
105
+
106
+ // Determine if we should verify the CA chain
107
+ // From PostgreSQL docs: "if a root CA file exists, the behavior of sslmode=require
108
+ // will be the same as that of verify-ca"
109
+ const hasLibpqCompatibility =
110
+ (sslmode === "require" || sslmode === "prefer") && caValue !== undefined;
111
+ const shouldVerifyCa = hasExplicitVerification || hasLibpqCompatibility;
112
+
113
+ // Determine if we should verify hostname
114
+ // - verify-full: verify both CA and hostname
115
+ // - verify-ca: verify CA only (skip hostname)
116
+ // - require/prefer with CA (libpq compat): behaves like verify-ca (skip hostname)
117
+ const shouldVerifyHostname = sslmode === "verify-full";
118
+
119
+ const ssl: {
120
+ rejectUnauthorized: boolean;
121
+ ca?: string;
122
+ cert?: string;
123
+ key?: string;
124
+ checkServerIdentity?: () => undefined;
125
+ } = {
126
+ rejectUnauthorized: shouldVerifyCa,
127
+ };
128
+
129
+ // Add CA certificate if verifying
130
+ if (shouldVerifyCa && caValue) {
131
+ ssl.ca = caValue;
132
+ }
133
+
134
+ // For verify-ca and libpq compatibility mode: skip hostname verification
135
+ // This matches PostgreSQL semantics where verify-ca only checks the CA chain
136
+ if (shouldVerifyCa && !shouldVerifyHostname) {
137
+ ssl.checkServerIdentity = () => undefined;
138
+ }
139
+
140
+ // Get client certificate (optional, for mutual TLS)
141
+ const certEnvVar =
142
+ connectionType === "source"
143
+ ? "PGDELTA_SOURCE_SSLCERT"
144
+ : "PGDELTA_TARGET_SSLCERT";
145
+ const certValue = await getCertValue(sslcert, certEnvVar);
146
+ if (certValue) {
147
+ ssl.cert = certValue;
148
+ }
149
+
150
+ // Get client key (optional, for mutual TLS, required if cert is provided)
151
+ const keyEnvVar =
152
+ connectionType === "source"
153
+ ? "PGDELTA_SOURCE_SSLKEY"
154
+ : "PGDELTA_TARGET_SSLKEY";
155
+ const keyValue = await getCertValue(sslkey, keyEnvVar);
156
+ if (keyValue) {
157
+ ssl.key = keyValue;
158
+ }
159
+
160
+ // Warn if cert is provided without key (or vice versa)
161
+ if ((ssl.cert && !ssl.key) || (!ssl.cert && ssl.key)) {
162
+ throw new Error(
163
+ "Both client certificate and key must be provided together for mutual TLS",
164
+ );
165
+ }
166
+
167
+ return { ssl, cleanedUrl };
168
+ }
169
+
170
+ // No sslmode specified or invalid value - explicitly disable SSL
171
+ return { cleanedUrl, ssl: false };
172
+ }
@@ -162,4 +162,10 @@ export interface CreatePlanOptions {
162
162
  serialize?: SerializeDSL | ChangeSerializer;
163
163
  /** Role to use when executing the migration (SET ROLE will be added to statements) */
164
164
  role?: string;
165
+ /**
166
+ * When true, don't subtract privileges covered by ALTER DEFAULT PRIVILEGES
167
+ * from explicit GRANTs during diffing. Use this for declarative export where
168
+ * the output must be self-contained and not rely on statement execution order.
169
+ */
170
+ skipDefaultPrivilegeSubtraction?: boolean;
165
171
  }
@@ -3,7 +3,8 @@
3
3
  */
4
4
 
5
5
  import type { PoolClient, PoolConfig } from "pg";
6
- import { Pool, types } from "pg";
6
+ import { escapeIdentifier, Pool, types } from "pg";
7
+ import { parseSslConfig } from "./plan/ssl-config.ts";
7
8
 
8
9
  // ============================================================================
9
10
  // Array Parser
@@ -103,6 +104,12 @@ types.setTypeParser(1007, (val: string) => parseArray(val, parseIntElement)); //
103
104
  // @ts-expect-error - pg types expects TypeId but raw OID numbers work fine
104
105
  types.setTypeParser(1016, (val: string) => parseArray(val, parseIntElement)); // int8[]
105
106
 
107
+ const DEFAULT_POOL_MAX = Number(process.env.PGDELTA_POOL_MAX) || 5;
108
+ const DEFAULT_CONNECTION_TIMEOUT_MS =
109
+ Number(process.env.PGDELTA_CONNECTION_TIMEOUT_MS) || 3_000;
110
+ const DEFAULT_CONNECT_TIMEOUT_MS =
111
+ Number(process.env.PGDELTA_CONNECT_TIMEOUT_MS) || 2_500;
112
+
106
113
  /**
107
114
  * Options for creating a Pool with event listeners.
108
115
  */
@@ -125,7 +132,12 @@ export function createPool(
125
132
  options?: CreatePoolOptions,
126
133
  ): Pool {
127
134
  const { onConnect, onError, onAcquire, onRemove, ...config } = options ?? {};
128
- const pool = new Pool({ connectionString, ...config });
135
+ const pool = new Pool({
136
+ connectionString,
137
+ max: DEFAULT_POOL_MAX,
138
+ connectionTimeoutMillis: DEFAULT_CONNECTION_TIMEOUT_MS,
139
+ ...config,
140
+ });
129
141
 
130
142
  if (onConnect) pool.on("connect", onConnect);
131
143
  if (onError) pool.on("error", onError);
@@ -149,6 +161,63 @@ export function createPool(
149
161
  * inside each `client.end()` callback — ensuring all sockets are
150
162
  * truly closed before it resolves.
151
163
  */
164
+ /**
165
+ * Create a pool from a connection URL with standard session setup:
166
+ * SSL parsing, search_path isolation, optional SET ROLE, and 57P01 suppression.
167
+ *
168
+ * Returns the pool and a `close` function that properly waits for all sockets
169
+ * to close (via {@link endPool}).
170
+ */
171
+ export async function createManagedPool(
172
+ url: string,
173
+ options?: { role?: string; label?: "source" | "target" },
174
+ ): Promise<{ pool: Pool; close: () => Promise<void> }> {
175
+ const sslConfig = await parseSslConfig(url, options?.label ?? "target");
176
+ const pool = createPool(sslConfig.cleanedUrl, {
177
+ ...(sslConfig.ssl !== undefined ? { ssl: sslConfig.ssl } : {}),
178
+ onError: (err: Error & { code?: string }) => {
179
+ if (err.code !== "57P01") {
180
+ console.error("Pool error:", err);
181
+ }
182
+ },
183
+ onConnect: async (client) => {
184
+ await client.query("SET search_path = ''");
185
+ if (options?.role) {
186
+ await client.query(`SET ROLE ${escapeIdentifier(options.role)}`);
187
+ }
188
+ },
189
+ });
190
+
191
+ // Eagerly validate connectivity so SSL/auth failures surface immediately
192
+ // instead of hanging on the first real query. node-pg's connectionTimeoutMillis
193
+ // is not reliably enforced under Bun when SSL negotiation hangs.
194
+ const label = options?.label ?? "target";
195
+ const timeoutMs = DEFAULT_CONNECT_TIMEOUT_MS;
196
+ try {
197
+ const client = await Promise.race([
198
+ pool.connect(),
199
+ new Promise<never>((_, reject) =>
200
+ setTimeout(
201
+ () =>
202
+ reject(
203
+ new Error(
204
+ `Connection to ${label} database timed out after ${timeoutMs}ms. ` +
205
+ `The server may require SSL, use an invalid certificate, or be unreachable.`,
206
+ ),
207
+ ),
208
+ timeoutMs,
209
+ ),
210
+ ),
211
+ ]);
212
+ client.release();
213
+ } catch (err) {
214
+ await pool.end().catch(() => {});
215
+ throw err;
216
+ }
217
+
218
+ return { pool, close: () => endPool(pool) };
219
+ }
220
+
152
221
  export function endPool(pool: Pool): Promise<void> {
153
222
  const clientCount = pool.totalCount;
154
223
 
@@ -87,7 +87,19 @@ export function convertExplicitRequirementsToConstraints(
87
87
 
88
88
  if (requiredIds.size === 0) continue;
89
89
 
90
+ // Collect dropped IDs for this change so we can skip requirements
91
+ // for stableIds that this change also drops. A change that drops a
92
+ // stableId should not depend on another change that creates the same
93
+ // stableId, because the entity already exists in the source database.
94
+ // This prevents false ordering constraints such as Grant → Revoke
95
+ // when both operate on the same ACL stableId.
96
+ const droppedIds = new Set<string>(phaseChanges[consumerIndex].drops);
97
+
90
98
  for (const requiredId of requiredIds) {
99
+ if (droppedIds.has(requiredId)) {
100
+ continue;
101
+ }
102
+
91
103
  const producerIndexes =
92
104
  graphData.changeIndexesByCreatedId.get(requiredId);
93
105
  if (!producerIndexes || producerIndexes.size === 0) continue;