@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.
- package/README.md +40 -23
- package/dist/cli/app.js +26 -3
- package/dist/cli/bin/cli.js +5 -0
- package/dist/cli/commands/catalog-export.d.ts +5 -0
- package/dist/cli/commands/catalog-export.js +64 -0
- package/dist/cli/commands/declarative-apply.d.ts +6 -0
- package/dist/cli/commands/declarative-apply.js +288 -0
- package/dist/cli/commands/declarative-export.d.ts +5 -0
- package/dist/cli/commands/declarative-export.js +245 -0
- package/dist/cli/commands/plan.js +19 -6
- package/dist/cli/exit-code.d.ts +2 -0
- package/dist/cli/exit-code.js +7 -0
- package/dist/cli/formatters/tree/tree.js +3 -2
- package/dist/cli/utils/apply-display.d.ts +52 -0
- package/dist/cli/utils/apply-display.js +183 -0
- package/dist/cli/utils/export-display.d.ts +43 -0
- package/dist/cli/utils/export-display.js +202 -0
- package/dist/cli/utils/resolve-input.d.ts +7 -0
- package/dist/cli/utils/resolve-input.js +13 -0
- package/dist/core/catalog-export/index.d.ts +11 -0
- package/dist/core/catalog-export/index.js +10 -0
- package/dist/core/catalog.diff.d.ts +1 -0
- package/dist/core/catalog.diff.js +64 -48
- package/dist/core/catalog.model.d.ts +14 -1
- package/dist/core/catalog.model.js +103 -1
- package/dist/core/catalog.snapshot.d.ts +66 -0
- package/dist/core/catalog.snapshot.js +206 -0
- package/dist/core/declarative-apply/discover-sql.d.ts +18 -0
- package/dist/core/declarative-apply/discover-sql.js +86 -0
- package/dist/core/declarative-apply/extract-catalog-providers.d.ts +23 -0
- package/dist/core/declarative-apply/extract-catalog-providers.js +159 -0
- package/dist/core/declarative-apply/index.d.ts +49 -0
- package/dist/core/declarative-apply/index.js +134 -0
- package/dist/core/declarative-apply/round-apply.d.ts +100 -0
- package/dist/core/declarative-apply/round-apply.js +378 -0
- package/dist/core/export/file-mapper.d.ts +71 -0
- package/dist/core/export/file-mapper.js +474 -0
- package/dist/core/export/grouper.d.ts +13 -0
- package/dist/core/export/grouper.js +76 -0
- package/dist/core/export/index.d.ts +45 -0
- package/dist/core/export/index.js +63 -0
- package/dist/core/export/types.d.ts +84 -0
- package/dist/core/export/types.js +25 -0
- package/dist/core/fixtures/empty-catalogs/postgres-15-16-baseline.json +287 -0
- package/dist/core/integrations/filter/dsl.d.ts +38 -1
- package/dist/core/integrations/filter/dsl.js +20 -2
- package/dist/core/integrations/filter/extractors.js +42 -0
- package/dist/core/integrations/integration-dsl.d.ts +10 -0
- package/dist/core/integrations/supabase.d.ts +8 -0
- package/dist/core/integrations/supabase.js +9 -0
- package/dist/core/objects/aggregate/aggregate.diff.d.ts +2 -8
- package/dist/core/objects/aggregate/aggregate.diff.js +16 -70
- package/dist/core/objects/aggregate/aggregate.model.d.ts +8 -8
- package/dist/core/objects/aggregate/aggregate.model.js +1 -1
- package/dist/core/objects/aggregate/changes/aggregate.create.js +1 -1
- package/dist/core/objects/aggregate/changes/aggregate.drop.js +1 -1
- package/dist/core/objects/base.privilege-diff.d.ts +38 -13
- package/dist/core/objects/base.privilege-diff.js +104 -22
- package/dist/core/objects/base.privilege.d.ts +1 -0
- package/dist/core/objects/base.privilege.js +9 -2
- package/dist/core/objects/collation/collation.diff.d.ts +2 -3
- package/dist/core/objects/diff-context.d.ts +15 -0
- package/dist/core/objects/diff-context.js +1 -0
- package/dist/core/objects/domain/changes/domain.create.js +4 -2
- package/dist/core/objects/domain/domain.diff.d.ts +2 -8
- package/dist/core/objects/domain/domain.diff.js +16 -77
- package/dist/core/objects/domain/domain.model.js +1 -1
- package/dist/core/objects/event-trigger/event-trigger.diff.d.ts +2 -3
- package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.d.ts +2 -8
- package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.js +13 -77
- package/dist/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.js +2 -2
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.d.ts +2 -8
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.js +16 -77
- package/dist/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.js +1 -1
- package/dist/core/objects/foreign-data-wrapper/server/server.diff.d.ts +2 -8
- package/dist/core/objects/foreign-data-wrapper/server/server.diff.js +13 -77
- package/dist/core/objects/language/language.diff.d.ts +2 -5
- package/dist/core/objects/language/language.diff.js +7 -39
- package/dist/core/objects/materialized-view/materialized-view.diff.d.ts +2 -8
- package/dist/core/objects/materialized-view/materialized-view.diff.js +16 -158
- package/dist/core/objects/materialized-view/materialized-view.model.d.ts +3 -3
- package/dist/core/objects/materialized-view/materialized-view.model.js +1 -1
- package/dist/core/objects/procedure/changes/procedure.alter.js +12 -12
- package/dist/core/objects/procedure/procedure.diff.d.ts +2 -8
- package/dist/core/objects/procedure/procedure.diff.js +16 -77
- package/dist/core/objects/procedure/procedure.model.d.ts +9 -9
- package/dist/core/objects/procedure/procedure.model.js +1 -1
- package/dist/core/objects/publication/changes/publication.alter.d.ts +0 -9
- package/dist/core/objects/publication/changes/publication.alter.js +0 -14
- package/dist/core/objects/publication/changes/publication.types.d.ts +2 -2
- package/dist/core/objects/publication/publication.diff.d.ts +2 -3
- package/dist/core/objects/publication/publication.diff.js +8 -13
- package/dist/core/objects/rls-policy/changes/rls-policy.alter.js +3 -3
- package/dist/core/objects/rls-policy/rls-policy.model.d.ts +2 -2
- package/dist/core/objects/role/role.diff.js +22 -1
- package/dist/core/objects/role/role.model.d.ts +4 -3
- package/dist/core/objects/role/role.model.js +118 -12
- package/dist/core/objects/rule/rule.model.d.ts +1 -1
- package/dist/core/objects/schema/schema.diff.d.ts +2 -8
- package/dist/core/objects/schema/schema.diff.js +16 -77
- package/dist/core/objects/schema/schema.model.js +1 -1
- package/dist/core/objects/sequence/sequence.diff.d.ts +2 -8
- package/dist/core/objects/sequence/sequence.diff.js +16 -79
- package/dist/core/objects/sequence/sequence.model.js +1 -1
- package/dist/core/objects/subscription/subscription.diff.d.ts +2 -3
- package/dist/core/objects/table/changes/table.create.js +3 -0
- package/dist/core/objects/table/table.diff.d.ts +2 -8
- package/dist/core/objects/table/table.diff.js +26 -157
- package/dist/core/objects/table/table.model.d.ts +23 -22
- package/dist/core/objects/table/table.model.js +1 -1
- package/dist/core/objects/trigger/changes/trigger.create.js +2 -4
- package/dist/core/objects/trigger/trigger.model.d.ts +8 -0
- package/dist/core/objects/trigger/trigger.model.js +11 -0
- package/dist/core/objects/type/composite-type/composite-type.diff.d.ts +2 -8
- package/dist/core/objects/type/composite-type/composite-type.diff.js +16 -77
- package/dist/core/objects/type/composite-type/composite-type.model.d.ts +3 -3
- package/dist/core/objects/type/composite-type/composite-type.model.js +2 -1
- package/dist/core/objects/type/enum/enum.diff.d.ts +2 -8
- package/dist/core/objects/type/enum/enum.diff.js +25 -112
- package/dist/core/objects/type/enum/enum.model.js +1 -1
- package/dist/core/objects/type/range/changes/range.create.js +6 -3
- package/dist/core/objects/type/range/range.diff.d.ts +2 -8
- package/dist/core/objects/type/range/range.diff.js +16 -77
- package/dist/core/objects/type/range/range.model.js +1 -1
- package/dist/core/objects/view/view.diff.d.ts +2 -8
- package/dist/core/objects/view/view.diff.js +16 -158
- package/dist/core/objects/view/view.model.d.ts +18 -4
- package/dist/core/objects/view/view.model.js +3 -13
- package/dist/core/plan/apply.js +9 -26
- package/dist/core/plan/create.d.ts +19 -6
- package/dist/core/plan/create.js +134 -174
- package/dist/core/plan/serialize.js +16 -4
- package/dist/core/plan/sql-format/fixtures.js +3 -5
- package/dist/core/plan/sql-format/keyword-case.js +26 -1
- package/dist/core/plan/ssl-config.d.ts +32 -0
- package/dist/core/plan/ssl-config.js +115 -0
- package/dist/core/plan/types.d.ts +6 -0
- package/dist/core/postgres-config.d.ts +14 -0
- package/dist/core/postgres-config.js +53 -2
- package/dist/core/sort/graph-builder.js +10 -0
- package/dist/core/sort/logical-sort.js +31 -23
- package/dist/core/test-utils/assert-valid-sql.d.ts +10 -0
- package/dist/core/test-utils/assert-valid-sql.js +19 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -1
- package/package.json +21 -4
- package/src/cli/app.ts +27 -3
- package/src/cli/bin/cli.ts +6 -0
- package/src/cli/commands/catalog-export.ts +78 -0
- package/src/cli/commands/declarative-apply.diagnostics.test.ts +77 -0
- package/src/cli/commands/declarative-apply.ts +380 -0
- package/src/cli/commands/declarative-export.ts +330 -0
- package/src/cli/commands/plan.ts +28 -7
- package/src/cli/exit-code.test.ts +19 -0
- package/src/cli/exit-code.ts +7 -0
- package/src/cli/formatters/tree/tree.ts +3 -2
- package/src/cli/utils/apply-display.test.ts +348 -0
- package/src/cli/utils/apply-display.ts +238 -0
- package/src/cli/utils/export-display.test.ts +103 -0
- package/src/cli/utils/export-display.ts +275 -0
- package/src/cli/utils/integrations.test.ts +44 -0
- package/src/cli/utils/resolve-input.test.ts +38 -0
- package/src/cli/utils/resolve-input.ts +17 -0
- package/src/core/catalog-export/index.ts +20 -0
- package/src/core/catalog.diff.ts +79 -78
- package/src/core/catalog.model.test.ts +122 -0
- package/src/core/catalog.model.ts +127 -1
- package/src/core/catalog.snapshot.test.ts +464 -0
- package/src/core/catalog.snapshot.ts +289 -0
- package/src/core/declarative-apply/discover-sql.test.ts +103 -0
- package/src/core/declarative-apply/discover-sql.ts +107 -0
- package/src/core/declarative-apply/extract-catalog-providers.ts +220 -0
- package/src/core/declarative-apply/index.test.ts +67 -0
- package/src/core/declarative-apply/index.ts +205 -0
- package/src/core/declarative-apply/round-apply.test.ts +504 -0
- package/src/core/declarative-apply/round-apply.ts +562 -0
- package/src/core/expand-replace-dependencies.test.ts +70 -0
- package/src/core/export/file-mapper.test.ts +816 -0
- package/src/core/export/file-mapper.ts +574 -0
- package/src/core/export/grouper.ts +108 -0
- package/src/core/export/index.ts +129 -0
- package/src/core/export/types.ts +104 -0
- package/src/core/fixtures/empty-catalogs/postgres-15-16-baseline.json +287 -0
- package/src/core/integrations/filter/dsl.test.ts +211 -0
- package/src/core/integrations/filter/dsl.ts +65 -3
- package/src/core/integrations/filter/extractors.test.ts +244 -0
- package/src/core/integrations/filter/extractors.ts +42 -0
- package/src/core/integrations/integration-dsl.ts +10 -0
- package/src/core/integrations/serialize/dsl.test.ts +91 -0
- package/src/core/integrations/supabase.ts +9 -0
- package/src/core/objects/aggregate/aggregate.diff.ts +39 -95
- package/src/core/objects/aggregate/aggregate.model.ts +1 -1
- package/src/core/objects/aggregate/changes/aggregate.alter.test.ts +3 -1
- package/src/core/objects/aggregate/changes/aggregate.comment.test.ts +5 -2
- package/src/core/objects/aggregate/changes/aggregate.create.test.ts +6 -3
- package/src/core/objects/aggregate/changes/aggregate.create.ts +1 -1
- package/src/core/objects/aggregate/changes/aggregate.drop.test.ts +7 -3
- package/src/core/objects/aggregate/changes/aggregate.drop.ts +1 -1
- package/src/core/objects/aggregate/changes/aggregate.privilege.test.ts +9 -3
- package/src/core/objects/base.privilege-diff.ts +178 -30
- package/src/core/objects/base.privilege.ts +9 -2
- package/src/core/objects/collation/changes/collation.alter.test.ts +7 -2
- package/src/core/objects/collation/changes/collation.create.test.ts +7 -2
- package/src/core/objects/collation/changes/collation.drop.test.ts +4 -1
- package/src/core/objects/collation/collation.diff.test.ts +9 -12
- package/src/core/objects/collation/collation.diff.ts +2 -1
- package/src/core/objects/diff-context.ts +16 -0
- package/src/core/objects/domain/changes/domain.alter.test.ts +28 -9
- package/src/core/objects/domain/changes/domain.create.test.ts +32 -2
- package/src/core/objects/domain/changes/domain.create.ts +7 -1
- package/src/core/objects/domain/changes/domain.drop.test.ts +4 -1
- package/src/core/objects/domain/domain.diff.ts +39 -102
- package/src/core/objects/domain/domain.model.ts +1 -1
- package/src/core/objects/event-trigger/changes/event-trigger.alter.test.ts +10 -3
- package/src/core/objects/event-trigger/changes/event-trigger.create.test.ts +4 -1
- package/src/core/objects/event-trigger/changes/event-trigger.drop.test.ts +4 -1
- package/src/core/objects/event-trigger/event-trigger.diff.test.ts +12 -7
- package/src/core/objects/event-trigger/event-trigger.diff.ts +2 -1
- package/src/core/objects/extension/changes/extension.alter.test.ts +7 -2
- package/src/core/objects/extension/changes/extension.create.test.ts +4 -1
- package/src/core/objects/extension/changes/extension.drop.test.ts +4 -1
- package/src/core/objects/extension/extension.model.test.ts +98 -0
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.alter.test.ts +16 -5
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.create.test.ts +51 -16
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/changes/foreign-data-wrapper.drop.test.ts +4 -1
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.test.ts +111 -4
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.diff.ts +31 -101
- package/src/core/objects/foreign-data-wrapper/foreign-data-wrapper/foreign-data-wrapper.model.ts +2 -2
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.alter.test.ts +46 -15
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.create.test.ts +13 -4
- package/src/core/objects/foreign-data-wrapper/foreign-table/changes/foreign-table.drop.test.ts +4 -1
- package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.diff.ts +39 -102
- package/src/core/objects/foreign-data-wrapper/foreign-table/foreign-table.model.ts +1 -1
- package/src/core/objects/foreign-data-wrapper/server/changes/server.alter.test.ts +22 -7
- package/src/core/objects/foreign-data-wrapper/server/changes/server.create.test.ts +19 -6
- package/src/core/objects/foreign-data-wrapper/server/changes/server.drop.test.ts +4 -1
- package/src/core/objects/foreign-data-wrapper/server/server.diff.test.ts +95 -0
- package/src/core/objects/foreign-data-wrapper/server/server.diff.ts +31 -101
- package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.alter.test.ts +13 -4
- package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.create.test.ts +16 -5
- package/src/core/objects/foreign-data-wrapper/user-mapping/changes/user-mapping.drop.test.ts +10 -3
- package/src/core/objects/index/changes/index.alter.test.ts +13 -4
- package/src/core/objects/index/changes/index.create.test.ts +4 -1
- package/src/core/objects/index/changes/index.drop.test.ts +4 -1
- package/src/core/objects/language/changes/language.alter.test.ts +4 -1
- package/src/core/objects/language/changes/language.create.test.ts +4 -1
- package/src/core/objects/language/changes/language.drop.test.ts +4 -1
- package/src/core/objects/language/language.diff.test.ts +86 -4
- package/src/core/objects/language/language.diff.ts +17 -49
- package/src/core/objects/materialized-view/changes/materialized-view.alter.test.ts +10 -3
- package/src/core/objects/materialized-view/changes/materialized-view.create.test.ts +7 -2
- package/src/core/objects/materialized-view/changes/materialized-view.drop.test.ts +4 -1
- package/src/core/objects/materialized-view/materialized-view.diff.test.ts +162 -0
- package/src/core/objects/materialized-view/materialized-view.diff.ts +41 -191
- package/src/core/objects/materialized-view/materialized-view.model.ts +1 -1
- package/src/core/objects/procedure/changes/procedure.alter.test.ts +121 -49
- package/src/core/objects/procedure/changes/procedure.alter.ts +15 -12
- package/src/core/objects/procedure/changes/procedure.create.test.ts +4 -1
- package/src/core/objects/procedure/changes/procedure.drop.test.ts +7 -2
- package/src/core/objects/procedure/procedure.diff.ts +39 -102
- package/src/core/objects/procedure/procedure.model.ts +1 -1
- package/src/core/objects/publication/changes/publication.alter.test.ts +15 -21
- package/src/core/objects/publication/changes/publication.alter.ts +0 -18
- package/src/core/objects/publication/changes/publication.comment.test.ts +5 -2
- package/src/core/objects/publication/changes/publication.create.test.ts +5 -2
- package/src/core/objects/publication/changes/publication.drop.test.ts +3 -1
- package/src/core/objects/publication/changes/publication.types.ts +0 -2
- package/src/core/objects/publication/publication.diff.test.ts +24 -19
- package/src/core/objects/publication/publication.diff.ts +9 -15
- package/src/core/objects/rls-policy/changes/rls-policy.alter.test.ts +31 -14
- package/src/core/objects/rls-policy/changes/rls-policy.alter.ts +3 -3
- package/src/core/objects/rls-policy/changes/rls-policy.create.test.ts +10 -3
- package/src/core/objects/rls-policy/changes/rls-policy.drop.test.ts +4 -1
- package/src/core/objects/role/changes/role.alter.test.ts +31 -15
- package/src/core/objects/role/changes/role.create.test.ts +6 -2
- package/src/core/objects/role/changes/role.drop.test.ts +4 -1
- package/src/core/objects/role/role.diff.test.ts +235 -0
- package/src/core/objects/role/role.diff.ts +21 -1
- package/src/core/objects/role/role.model.ts +122 -14
- package/src/core/objects/rule/changes/rule.alter.test.ts +7 -3
- package/src/core/objects/rule/changes/rule.comment.test.ts +5 -2
- package/src/core/objects/rule/changes/rule.create.test.ts +6 -2
- package/src/core/objects/rule/changes/rule.drop.test.ts +3 -1
- package/src/core/objects/schema/changes/schema.alter.test.ts +4 -1
- package/src/core/objects/schema/changes/schema.create.test.ts +4 -1
- package/src/core/objects/schema/changes/schema.drop.test.ts +4 -1
- package/src/core/objects/schema/schema.diff.ts +39 -102
- package/src/core/objects/schema/schema.model.ts +1 -1
- package/src/core/objects/sequence/changes/sequence.alter.test.ts +11 -5
- package/src/core/objects/sequence/changes/sequence.create.test.ts +8 -3
- package/src/core/objects/sequence/changes/sequence.drop.test.ts +4 -1
- package/src/core/objects/sequence/sequence.diff.test.ts +114 -0
- package/src/core/objects/sequence/sequence.diff.ts +39 -104
- package/src/core/objects/sequence/sequence.model.ts +1 -1
- package/src/core/objects/subscription/changes/subscription.alter.test.ts +15 -5
- package/src/core/objects/subscription/changes/subscription.comment.test.ts +5 -2
- package/src/core/objects/subscription/changes/subscription.create.test.ts +5 -2
- package/src/core/objects/subscription/changes/subscription.drop.test.ts +3 -1
- package/src/core/objects/subscription/subscription.diff.test.ts +16 -11
- package/src/core/objects/subscription/subscription.diff.ts +2 -1
- package/src/core/objects/table/changes/table.alter.test.ts +38 -15
- package/src/core/objects/table/changes/table.create.test.ts +41 -3
- package/src/core/objects/table/changes/table.create.ts +4 -0
- package/src/core/objects/table/changes/table.drop.test.ts +3 -1
- package/src/core/objects/table/table.diff.test.ts +157 -0
- package/src/core/objects/table/table.diff.ts +54 -190
- package/src/core/objects/table/table.model.ts +1 -1
- package/src/core/objects/trigger/changes/trigger.alter.test.ts +8 -4
- package/src/core/objects/trigger/changes/trigger.create.test.ts +5 -1
- package/src/core/objects/trigger/changes/trigger.create.ts +7 -4
- package/src/core/objects/trigger/changes/trigger.drop.test.ts +5 -1
- package/src/core/objects/trigger/trigger.diff.test.ts +1 -0
- package/src/core/objects/trigger/trigger.model.ts +12 -0
- package/src/core/objects/type/composite-type/changes/composite-type.alter.test.ts +10 -4
- package/src/core/objects/type/composite-type/changes/composite-type.create.test.ts +7 -2
- package/src/core/objects/type/composite-type/changes/composite-type.drop.test.ts +4 -1
- package/src/core/objects/type/composite-type/composite-type.diff.test.ts +78 -0
- package/src/core/objects/type/composite-type/composite-type.diff.ts +39 -101
- package/src/core/objects/type/composite-type/composite-type.model.ts +2 -1
- package/src/core/objects/type/enum/changes/enum.alter.test.ts +14 -5
- package/src/core/objects/type/enum/changes/enum.create.test.ts +4 -1
- package/src/core/objects/type/enum/changes/enum.drop.test.ts +4 -1
- package/src/core/objects/type/enum/enum.diff.test.ts +181 -0
- package/src/core/objects/type/enum/enum.diff.ts +58 -146
- package/src/core/objects/type/enum/enum.model.ts +1 -1
- package/src/core/objects/type/range/changes/range.alter.test.ts +3 -1
- package/src/core/objects/type/range/changes/range.create.test.ts +5 -2
- package/src/core/objects/type/range/changes/range.create.ts +6 -2
- package/src/core/objects/type/range/changes/range.drop.test.ts +3 -1
- package/src/core/objects/type/range/range.diff.test.ts +77 -0
- package/src/core/objects/type/range/range.diff.ts +39 -101
- package/src/core/objects/type/range/range.model.ts +1 -1
- package/src/core/objects/view/changes/view.alter.test.ts +8 -3
- package/src/core/objects/view/changes/view.create.test.ts +7 -2
- package/src/core/objects/view/changes/view.drop.test.ts +4 -1
- package/src/core/objects/view/view.diff.test.ts +82 -0
- package/src/core/objects/view/view.diff.ts +41 -191
- package/src/core/objects/view/view.model.ts +3 -17
- package/src/core/plan/apply.ts +9 -27
- package/src/core/plan/create.ts +173 -237
- package/src/core/plan/serialize.test.ts +317 -0
- package/src/core/plan/serialize.ts +18 -4
- package/src/core/plan/sql-format/fixtures.ts +2 -5
- package/src/core/plan/sql-format/format-lowercase-coverage.test.ts +52 -0
- package/src/core/plan/sql-format/format-off.test.ts +14 -17
- package/src/core/plan/sql-format/format-pretty-lower-leading.test.ts +27 -22
- package/src/core/plan/sql-format/format-pretty-narrow.test.ts +17 -21
- package/src/core/plan/sql-format/format-pretty-preserve.test.ts +25 -20
- package/src/core/plan/sql-format/format-pretty-upper.test.ts +23 -20
- package/src/core/plan/sql-format/keyword-case.ts +36 -1
- package/src/core/plan/ssl-config.ts +172 -0
- package/src/core/plan/types.ts +6 -0
- package/src/core/postgres-config.ts +71 -2
- package/src/core/sort/graph-builder.ts +12 -0
- package/src/core/sort/logical-sort.test.ts +371 -0
- package/src/core/sort/logical-sort.ts +32 -25
- package/src/core/sort/topological-sort.test.ts +275 -0
- package/src/core/test-utils/assert-valid-sql.ts +20 -0
- package/src/index.ts +26 -2
|
@@ -198,6 +198,27 @@ const STRUCTURAL_TOP_LEVEL_KEYWORDS = new Set([
|
|
|
198
198
|
"WRAPPER",
|
|
199
199
|
"MAPPING",
|
|
200
200
|
]);
|
|
201
|
+
const EMPTY_SCOPED_SET = new Set();
|
|
202
|
+
const ALTER_DEFAULT_PRIVILEGES_KEYWORDS = new Set([
|
|
203
|
+
"PUBLIC",
|
|
204
|
+
"SEQUENCES",
|
|
205
|
+
"ROUTINES",
|
|
206
|
+
"TYPES",
|
|
207
|
+
"SCHEMAS",
|
|
208
|
+
]);
|
|
209
|
+
const GRANT_REVOKE_KEYWORDS = new Set(["PUBLIC"]);
|
|
210
|
+
function getStatementScopedKeywords(topLevelTokens) {
|
|
211
|
+
const first = topLevelTokens[0]?.token.upper;
|
|
212
|
+
const second = topLevelTokens[1]?.token.upper;
|
|
213
|
+
const third = topLevelTokens[2]?.token.upper;
|
|
214
|
+
if (first === "ALTER" && second === "DEFAULT" && third === "PRIVILEGES") {
|
|
215
|
+
return ALTER_DEFAULT_PRIVILEGES_KEYWORDS;
|
|
216
|
+
}
|
|
217
|
+
if (first === "GRANT" || first === "REVOKE") {
|
|
218
|
+
return GRANT_REVOKE_KEYWORDS;
|
|
219
|
+
}
|
|
220
|
+
return EMPTY_SCOPED_SET;
|
|
221
|
+
}
|
|
201
222
|
const ALTER_TYPE_BOUNDARY_KEYWORDS = new Set([
|
|
202
223
|
"COLLATE",
|
|
203
224
|
"USING",
|
|
@@ -259,6 +280,7 @@ function collectCaseableTokenStarts(statement, tokens) {
|
|
|
259
280
|
if (topLevelTokens.length === 0)
|
|
260
281
|
return caseable;
|
|
261
282
|
const command = topLevelTokens[0].token.upper;
|
|
283
|
+
const scopedKeywords = getStatementScopedKeywords(topLevelTokens);
|
|
262
284
|
const objectNameTokenIndexes = new Set();
|
|
263
285
|
for (let topIndex = 0; topIndex < topLevelTokens.length; topIndex += 1) {
|
|
264
286
|
if (isLikelyObjectNameToken(command, topLevelTokens, topIndex)) {
|
|
@@ -268,7 +290,7 @@ function collectCaseableTokenStarts(statement, tokens) {
|
|
|
268
290
|
for (let index = 0; index < tokens.length; index += 1) {
|
|
269
291
|
const token = tokens[index];
|
|
270
292
|
const upper = token.upper;
|
|
271
|
-
if (!STRUCTURAL_TOP_LEVEL_KEYWORDS.has(upper))
|
|
293
|
+
if (!STRUCTURAL_TOP_LEVEL_KEYWORDS.has(upper) && !scopedKeywords.has(upper))
|
|
272
294
|
continue;
|
|
273
295
|
if (objectNameTokenIndexes.has(index))
|
|
274
296
|
continue;
|
|
@@ -379,6 +401,9 @@ function isCaseableInContext(command, upper, prev) {
|
|
|
379
401
|
if (upper === "IDENTITY") {
|
|
380
402
|
return prev === "REPLICA" || prev === "AS";
|
|
381
403
|
}
|
|
404
|
+
if (upper === "PUBLIC") {
|
|
405
|
+
return prev === "TO" || prev === "FROM";
|
|
406
|
+
}
|
|
382
407
|
if (upper === "OR") {
|
|
383
408
|
return true;
|
|
384
409
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
/** Parsed SSL options for the pg client plus URL with SSL params stripped (internal). */
|
|
8
|
+
type SslConfig = {
|
|
9
|
+
ssl?: boolean | {
|
|
10
|
+
rejectUnauthorized: boolean;
|
|
11
|
+
ca?: string;
|
|
12
|
+
cert?: string;
|
|
13
|
+
key?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Custom server identity check function.
|
|
16
|
+
* Used to skip hostname verification for verify-ca mode.
|
|
17
|
+
* Returns undefined to indicate success (no error).
|
|
18
|
+
*/
|
|
19
|
+
checkServerIdentity?: () => undefined;
|
|
20
|
+
};
|
|
21
|
+
cleanedUrl: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Parse SSL configuration from a PostgreSQL connection URL.
|
|
25
|
+
* Supports sslmode (require, verify-ca, verify-full, prefer, disable).
|
|
26
|
+
* Certificates can be provided via:
|
|
27
|
+
* - Query string parameters (file paths): sslrootcert, sslcert, sslkey (preferred)
|
|
28
|
+
* - Environment variables (content): PGDELTA_SOURCE_SSLROOTCERT/SSLCERT/SSLKEY or PGDELTA_TARGET_SSLROOTCERT/SSLCERT/SSLKEY
|
|
29
|
+
* Returns SSL options for the postgres.js library and a cleaned URL without SSL-related query parameters.
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseSslConfig(url: string, connectionType: "source" | "target"): Promise<SslConfig>;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
/**
|
|
9
|
+
* Parse SSL configuration from a PostgreSQL connection URL.
|
|
10
|
+
* Supports sslmode (require, verify-ca, verify-full, prefer, disable).
|
|
11
|
+
* Certificates can be provided via:
|
|
12
|
+
* - Query string parameters (file paths): sslrootcert, sslcert, sslkey (preferred)
|
|
13
|
+
* - Environment variables (content): PGDELTA_SOURCE_SSLROOTCERT/SSLCERT/SSLKEY or PGDELTA_TARGET_SSLROOTCERT/SSLCERT/SSLKEY
|
|
14
|
+
* Returns SSL options for the postgres.js library and a cleaned URL without SSL-related query parameters.
|
|
15
|
+
*/
|
|
16
|
+
export async function parseSslConfig(url, connectionType) {
|
|
17
|
+
const urlObj = new URL(url);
|
|
18
|
+
const sslmode = urlObj.searchParams.get("sslmode");
|
|
19
|
+
const sslrootcert = urlObj.searchParams.get("sslrootcert");
|
|
20
|
+
const sslcert = urlObj.searchParams.get("sslcert");
|
|
21
|
+
const sslkey = urlObj.searchParams.get("sslkey");
|
|
22
|
+
// Remove SSL-related query parameters since we parse them ourselves
|
|
23
|
+
urlObj.searchParams.delete("sslmode");
|
|
24
|
+
urlObj.searchParams.delete("sslrootcert");
|
|
25
|
+
urlObj.searchParams.delete("sslcert");
|
|
26
|
+
urlObj.searchParams.delete("sslkey");
|
|
27
|
+
const cleanedUrl = urlObj.toString();
|
|
28
|
+
// Handle different SSL modes
|
|
29
|
+
if (sslmode === "disable") {
|
|
30
|
+
return { cleanedUrl, ssl: false };
|
|
31
|
+
}
|
|
32
|
+
if (sslmode === "require" ||
|
|
33
|
+
sslmode === "prefer" ||
|
|
34
|
+
sslmode === "verify-ca" ||
|
|
35
|
+
sslmode === "verify-full") {
|
|
36
|
+
// Helper function to get certificate value: query param (file path) takes precedence over env var (content)
|
|
37
|
+
const getCertValue = async (queryParam, envVarName) => {
|
|
38
|
+
// Prefer query parameter (file path)
|
|
39
|
+
if (queryParam) {
|
|
40
|
+
try {
|
|
41
|
+
return await readFile(queryParam, "utf-8");
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw new Error(`Failed to read certificate file '${queryParam}': ${error instanceof Error ? error.message : String(error)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Fallback to environment variable (content)
|
|
48
|
+
const envValue = process.env[envVarName];
|
|
49
|
+
return envValue || undefined;
|
|
50
|
+
};
|
|
51
|
+
const hasExplicitVerification = sslmode === "verify-ca" || sslmode === "verify-full";
|
|
52
|
+
// Get CA certificate value.
|
|
53
|
+
// - verify-ca/verify-full: check query param first, then env var
|
|
54
|
+
// - require/prefer: only check query param (libpq backward compatibility
|
|
55
|
+
// requires an explicit root CA *file*, not a global env var)
|
|
56
|
+
const caEnvVar = connectionType === "source"
|
|
57
|
+
? "PGDELTA_SOURCE_SSLROOTCERT"
|
|
58
|
+
: "PGDELTA_TARGET_SSLROOTCERT";
|
|
59
|
+
let caValue;
|
|
60
|
+
if (sslrootcert) {
|
|
61
|
+
// Explicit file path in query param — always honour it
|
|
62
|
+
caValue = await getCertValue(sslrootcert, caEnvVar);
|
|
63
|
+
}
|
|
64
|
+
else if (hasExplicitVerification) {
|
|
65
|
+
// verify-ca / verify-full without file path — fall back to env var
|
|
66
|
+
caValue = await getCertValue(null, caEnvVar);
|
|
67
|
+
}
|
|
68
|
+
// require/prefer without sslrootcert: no CA cert, no verification
|
|
69
|
+
// Determine if we should verify the CA chain
|
|
70
|
+
// From PostgreSQL docs: "if a root CA file exists, the behavior of sslmode=require
|
|
71
|
+
// will be the same as that of verify-ca"
|
|
72
|
+
const hasLibpqCompatibility = (sslmode === "require" || sslmode === "prefer") && caValue !== undefined;
|
|
73
|
+
const shouldVerifyCa = hasExplicitVerification || hasLibpqCompatibility;
|
|
74
|
+
// Determine if we should verify hostname
|
|
75
|
+
// - verify-full: verify both CA and hostname
|
|
76
|
+
// - verify-ca: verify CA only (skip hostname)
|
|
77
|
+
// - require/prefer with CA (libpq compat): behaves like verify-ca (skip hostname)
|
|
78
|
+
const shouldVerifyHostname = sslmode === "verify-full";
|
|
79
|
+
const ssl = {
|
|
80
|
+
rejectUnauthorized: shouldVerifyCa,
|
|
81
|
+
};
|
|
82
|
+
// Add CA certificate if verifying
|
|
83
|
+
if (shouldVerifyCa && caValue) {
|
|
84
|
+
ssl.ca = caValue;
|
|
85
|
+
}
|
|
86
|
+
// For verify-ca and libpq compatibility mode: skip hostname verification
|
|
87
|
+
// This matches PostgreSQL semantics where verify-ca only checks the CA chain
|
|
88
|
+
if (shouldVerifyCa && !shouldVerifyHostname) {
|
|
89
|
+
ssl.checkServerIdentity = () => undefined;
|
|
90
|
+
}
|
|
91
|
+
// Get client certificate (optional, for mutual TLS)
|
|
92
|
+
const certEnvVar = connectionType === "source"
|
|
93
|
+
? "PGDELTA_SOURCE_SSLCERT"
|
|
94
|
+
: "PGDELTA_TARGET_SSLCERT";
|
|
95
|
+
const certValue = await getCertValue(sslcert, certEnvVar);
|
|
96
|
+
if (certValue) {
|
|
97
|
+
ssl.cert = certValue;
|
|
98
|
+
}
|
|
99
|
+
// Get client key (optional, for mutual TLS, required if cert is provided)
|
|
100
|
+
const keyEnvVar = connectionType === "source"
|
|
101
|
+
? "PGDELTA_SOURCE_SSLKEY"
|
|
102
|
+
: "PGDELTA_TARGET_SSLKEY";
|
|
103
|
+
const keyValue = await getCertValue(sslkey, keyEnvVar);
|
|
104
|
+
if (keyValue) {
|
|
105
|
+
ssl.key = keyValue;
|
|
106
|
+
}
|
|
107
|
+
// Warn if cert is provided without key (or vice versa)
|
|
108
|
+
if ((ssl.cert && !ssl.key) || (!ssl.cert && ssl.key)) {
|
|
109
|
+
throw new Error("Both client certificate and key must be provided together for mutual TLS");
|
|
110
|
+
}
|
|
111
|
+
return { ssl, cleanedUrl };
|
|
112
|
+
}
|
|
113
|
+
// No sslmode specified or invalid value - explicitly disable SSL
|
|
114
|
+
return { cleanedUrl, ssl: false };
|
|
115
|
+
}
|
|
@@ -138,5 +138,11 @@ export interface CreatePlanOptions {
|
|
|
138
138
|
serialize?: SerializeDSL | ChangeSerializer;
|
|
139
139
|
/** Role to use when executing the migration (SET ROLE will be added to statements) */
|
|
140
140
|
role?: string;
|
|
141
|
+
/**
|
|
142
|
+
* When true, don't subtract privileges covered by ALTER DEFAULT PRIVILEGES
|
|
143
|
+
* from explicit GRANTs during diffing. Use this for declarative export where
|
|
144
|
+
* the output must be self-contained and not rely on statement execution order.
|
|
145
|
+
*/
|
|
146
|
+
skipDefaultPrivilegeSubtraction?: boolean;
|
|
141
147
|
}
|
|
142
148
|
export {};
|
|
@@ -34,5 +34,19 @@ export declare function createPool(connectionString: string, options?: CreatePoo
|
|
|
34
34
|
* inside each `client.end()` callback — ensuring all sockets are
|
|
35
35
|
* truly closed before it resolves.
|
|
36
36
|
*/
|
|
37
|
+
/**
|
|
38
|
+
* Create a pool from a connection URL with standard session setup:
|
|
39
|
+
* SSL parsing, search_path isolation, optional SET ROLE, and 57P01 suppression.
|
|
40
|
+
*
|
|
41
|
+
* Returns the pool and a `close` function that properly waits for all sockets
|
|
42
|
+
* to close (via {@link endPool}).
|
|
43
|
+
*/
|
|
44
|
+
export declare function createManagedPool(url: string, options?: {
|
|
45
|
+
role?: string;
|
|
46
|
+
label?: "source" | "target";
|
|
47
|
+
}): Promise<{
|
|
48
|
+
pool: Pool;
|
|
49
|
+
close: () => Promise<void>;
|
|
50
|
+
}>;
|
|
37
51
|
export declare function endPool(pool: Pool): Promise<void>;
|
|
38
52
|
export {};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PostgreSQL connection configuration with custom type handlers.
|
|
3
3
|
*/
|
|
4
|
-
import { Pool, types } from "pg";
|
|
4
|
+
import { escapeIdentifier, Pool, types } from "pg";
|
|
5
|
+
import { parseSslConfig } from "./plan/ssl-config.js";
|
|
5
6
|
// ============================================================================
|
|
6
7
|
// Array Parser
|
|
7
8
|
// ============================================================================
|
|
@@ -92,12 +93,20 @@ types.setTypeParser(1005, (val) => parseArray(val, parseIntElement)); // int2[]
|
|
|
92
93
|
types.setTypeParser(1007, (val) => parseArray(val, parseIntElement)); // int4[]
|
|
93
94
|
// @ts-expect-error - pg types expects TypeId but raw OID numbers work fine
|
|
94
95
|
types.setTypeParser(1016, (val) => parseArray(val, parseIntElement)); // int8[]
|
|
96
|
+
const DEFAULT_POOL_MAX = Number(process.env.PGDELTA_POOL_MAX) || 5;
|
|
97
|
+
const DEFAULT_CONNECTION_TIMEOUT_MS = Number(process.env.PGDELTA_CONNECTION_TIMEOUT_MS) || 3_000;
|
|
98
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = Number(process.env.PGDELTA_CONNECT_TIMEOUT_MS) || 2_500;
|
|
95
99
|
/**
|
|
96
100
|
* Create a Pool with custom type handlers and optional event listeners.
|
|
97
101
|
*/
|
|
98
102
|
export function createPool(connectionString, options) {
|
|
99
103
|
const { onConnect, onError, onAcquire, onRemove, ...config } = options ?? {};
|
|
100
|
-
const pool = new Pool({
|
|
104
|
+
const pool = new Pool({
|
|
105
|
+
connectionString,
|
|
106
|
+
max: DEFAULT_POOL_MAX,
|
|
107
|
+
connectionTimeoutMillis: DEFAULT_CONNECTION_TIMEOUT_MS,
|
|
108
|
+
...config,
|
|
109
|
+
});
|
|
101
110
|
if (onConnect)
|
|
102
111
|
pool.on("connect", onConnect);
|
|
103
112
|
if (onError)
|
|
@@ -122,6 +131,48 @@ export function createPool(connectionString, options) {
|
|
|
122
131
|
* inside each `client.end()` callback — ensuring all sockets are
|
|
123
132
|
* truly closed before it resolves.
|
|
124
133
|
*/
|
|
134
|
+
/**
|
|
135
|
+
* Create a pool from a connection URL with standard session setup:
|
|
136
|
+
* SSL parsing, search_path isolation, optional SET ROLE, and 57P01 suppression.
|
|
137
|
+
*
|
|
138
|
+
* Returns the pool and a `close` function that properly waits for all sockets
|
|
139
|
+
* to close (via {@link endPool}).
|
|
140
|
+
*/
|
|
141
|
+
export async function createManagedPool(url, options) {
|
|
142
|
+
const sslConfig = await parseSslConfig(url, options?.label ?? "target");
|
|
143
|
+
const pool = createPool(sslConfig.cleanedUrl, {
|
|
144
|
+
...(sslConfig.ssl !== undefined ? { ssl: sslConfig.ssl } : {}),
|
|
145
|
+
onError: (err) => {
|
|
146
|
+
if (err.code !== "57P01") {
|
|
147
|
+
console.error("Pool error:", err);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
onConnect: async (client) => {
|
|
151
|
+
await client.query("SET search_path = ''");
|
|
152
|
+
if (options?.role) {
|
|
153
|
+
await client.query(`SET ROLE ${escapeIdentifier(options.role)}`);
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
// Eagerly validate connectivity so SSL/auth failures surface immediately
|
|
158
|
+
// instead of hanging on the first real query. node-pg's connectionTimeoutMillis
|
|
159
|
+
// is not reliably enforced under Bun when SSL negotiation hangs.
|
|
160
|
+
const label = options?.label ?? "target";
|
|
161
|
+
const timeoutMs = DEFAULT_CONNECT_TIMEOUT_MS;
|
|
162
|
+
try {
|
|
163
|
+
const client = await Promise.race([
|
|
164
|
+
pool.connect(),
|
|
165
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Connection to ${label} database timed out after ${timeoutMs}ms. ` +
|
|
166
|
+
`The server may require SSL, use an invalid certificate, or be unreachable.`)), timeoutMs)),
|
|
167
|
+
]);
|
|
168
|
+
client.release();
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
await pool.end().catch(() => { });
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
return { pool, close: () => endPool(pool) };
|
|
175
|
+
}
|
|
125
176
|
export function endPool(pool) {
|
|
126
177
|
const clientCount = pool.totalCount;
|
|
127
178
|
if (clientCount === 0) {
|
|
@@ -56,7 +56,17 @@ export function convertExplicitRequirementsToConstraints(phaseChanges, graphData
|
|
|
56
56
|
const requiredIds = graphData.explicitRequirementSets[consumerIndex];
|
|
57
57
|
if (requiredIds.size === 0)
|
|
58
58
|
continue;
|
|
59
|
+
// Collect dropped IDs for this change so we can skip requirements
|
|
60
|
+
// for stableIds that this change also drops. A change that drops a
|
|
61
|
+
// stableId should not depend on another change that creates the same
|
|
62
|
+
// stableId, because the entity already exists in the source database.
|
|
63
|
+
// This prevents false ordering constraints such as Grant → Revoke
|
|
64
|
+
// when both operate on the same ACL stableId.
|
|
65
|
+
const droppedIds = new Set(phaseChanges[consumerIndex].drops);
|
|
59
66
|
for (const requiredId of requiredIds) {
|
|
67
|
+
if (droppedIds.has(requiredId)) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
60
70
|
const producerIndexes = graphData.changeIndexesByCreatedId.get(requiredId);
|
|
61
71
|
if (!producerIndexes || producerIndexes.size === 0)
|
|
62
72
|
continue;
|
|
@@ -145,6 +145,25 @@ function getMainStableId(change) {
|
|
|
145
145
|
}
|
|
146
146
|
return null;
|
|
147
147
|
}
|
|
148
|
+
// For default_privilege operations: group by role + schema combination (before CREATE so we group and use tiebreaker)
|
|
149
|
+
if (change.scope === "default_privilege") {
|
|
150
|
+
if (change.requires.length > 0) {
|
|
151
|
+
let grantingRole = null;
|
|
152
|
+
let schemaId = null;
|
|
153
|
+
for (const id of change.requires) {
|
|
154
|
+
if (id.startsWith("role:")) {
|
|
155
|
+
grantingRole = id;
|
|
156
|
+
}
|
|
157
|
+
else if (id.startsWith("schema:")) {
|
|
158
|
+
schemaId = id;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (schemaId && grantingRole) {
|
|
162
|
+
return `${grantingRole}:${schemaId}`;
|
|
163
|
+
}
|
|
164
|
+
return grantingRole ?? null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
148
167
|
// For CREATE operations: check if creating a constraint (sub-entity of table)
|
|
149
168
|
if (change.operation === "create" && change.creates.length > 0) {
|
|
150
169
|
// Iterate through creates to find the first non-metadata stable ID
|
|
@@ -182,29 +201,6 @@ function getMainStableId(change) {
|
|
|
182
201
|
// Fallback: if all drops are metadata, use first
|
|
183
202
|
return change.drops[0] ?? null;
|
|
184
203
|
}
|
|
185
|
-
// For default_privilege operations: group by role + schema combination
|
|
186
|
-
// This groups all "FOR ROLE X IN SCHEMA Y" statements together
|
|
187
|
-
if (change.scope === "default_privilege") {
|
|
188
|
-
if (change.requires.length > 0) {
|
|
189
|
-
// Iterate through requires to find role and schema
|
|
190
|
-
let grantingRole = null;
|
|
191
|
-
let schemaId = null;
|
|
192
|
-
for (const id of change.requires) {
|
|
193
|
-
if (id.startsWith("role:")) {
|
|
194
|
-
grantingRole = id;
|
|
195
|
-
}
|
|
196
|
-
else if (id.startsWith("schema:")) {
|
|
197
|
-
schemaId = id;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
if (schemaId && grantingRole) {
|
|
201
|
-
// Create composite key: "role:postgres:schema:public"
|
|
202
|
-
return `${grantingRole}:${schemaId}`;
|
|
203
|
-
}
|
|
204
|
-
// If no schema, just group by role
|
|
205
|
-
return grantingRole ?? null;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
204
|
// For ALTER operations: check if creating/dropping a constraint
|
|
209
205
|
// Skip this for privilege/comment/default_privilege scopes (handled above)
|
|
210
206
|
if (change.operation === "alter") {
|
|
@@ -525,6 +521,18 @@ function sortPhase(changes, phase) {
|
|
|
525
521
|
if (operationOrderA !== operationOrderB) {
|
|
526
522
|
return operationOrderA - operationOrderB;
|
|
527
523
|
}
|
|
524
|
+
// 6b. For default_privilege: deterministic tiebreaker by objtype then grantee (canonical order for objtype)
|
|
525
|
+
if (scopeA === "default_privilege" && scopeB === "default_privilege") {
|
|
526
|
+
const defPrivA = changeA;
|
|
527
|
+
const defPrivB = changeB;
|
|
528
|
+
const objtypeOrder = (code) => ({ n: 0, r: 1, S: 2, f: 3, T: 4 })[code] ?? 99;
|
|
529
|
+
const objtypeCompare = objtypeOrder(defPrivA.objtype) - objtypeOrder(defPrivB.objtype);
|
|
530
|
+
if (objtypeCompare !== 0)
|
|
531
|
+
return objtypeCompare;
|
|
532
|
+
const granteeCompare = defPrivA.grantee.localeCompare(defPrivB.grantee);
|
|
533
|
+
if (granteeCompare !== 0)
|
|
534
|
+
return granteeCompare;
|
|
535
|
+
}
|
|
528
536
|
// 7. Preserve original order (stability)
|
|
529
537
|
return a.originalIndex - b.originalIndex;
|
|
530
538
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assert that the given SQL string is syntactically valid PostgreSQL.
|
|
3
|
+
*
|
|
4
|
+
* Uses the PostgreSQL parser from `@supabase/pg-topo` to ensure that
|
|
5
|
+
* serialized DDL statements are syntactically correct. This catches
|
|
6
|
+
* issues like malformed function signatures, missing keywords, etc.
|
|
7
|
+
*
|
|
8
|
+
* @param sql - The SQL string to validate (typically from `change.serialize()`).
|
|
9
|
+
*/
|
|
10
|
+
export declare function assertValidSql(sql: string): Promise<void>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { validateSqlSyntax } from "@supabase/pg-topo";
|
|
2
|
+
/**
|
|
3
|
+
* Assert that the given SQL string is syntactically valid PostgreSQL.
|
|
4
|
+
*
|
|
5
|
+
* Uses the PostgreSQL parser from `@supabase/pg-topo` to ensure that
|
|
6
|
+
* serialized DDL statements are syntactically correct. This catches
|
|
7
|
+
* issues like malformed function signatures, missing keywords, etc.
|
|
8
|
+
*
|
|
9
|
+
* @param sql - The SQL string to validate (typically from `change.serialize()`).
|
|
10
|
+
*/
|
|
11
|
+
export async function assertValidSql(sql) {
|
|
12
|
+
try {
|
|
13
|
+
await validateSqlSyntax(sql);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
const message = error instanceof Error ? error.message : "Unknown parser error";
|
|
17
|
+
throw new Error(`Invalid SQL syntax: ${message}\nSQL: ${sql}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This module exports the public API for the pg-delta library.
|
|
5
5
|
*/
|
|
6
|
+
export { Catalog, createEmptyCatalog, extractCatalog, } from "./core/catalog.model.ts";
|
|
7
|
+
export type { CatalogSnapshot } from "./core/catalog.snapshot.ts";
|
|
8
|
+
export { deserializeCatalog, serializeCatalog, stringifyCatalogSnapshot, } from "./core/catalog.snapshot.ts";
|
|
9
|
+
export { exportDeclarativeSchema } from "./core/export/index.ts";
|
|
10
|
+
export type { DeclarativeSchemaOutput, FileCategory, FileEntry, FileMetadata, } from "./core/export/types.ts";
|
|
6
11
|
export type { IntegrationDSL } from "./core/integrations/integration-dsl.ts";
|
|
7
12
|
export { applyPlan } from "./core/plan/apply.ts";
|
|
13
|
+
export type { CatalogInput } from "./core/plan/create.ts";
|
|
8
14
|
export { createPlan } from "./core/plan/create.ts";
|
|
9
15
|
export type { SqlFormatOptions } from "./core/plan/sql-format.ts";
|
|
10
16
|
export { formatSqlStatements } from "./core/plan/sql-format.ts";
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* This module exports the public API for the pg-delta library.
|
|
5
5
|
*/
|
|
6
|
+
// Catalog model and extraction
|
|
7
|
+
export { Catalog, createEmptyCatalog, extractCatalog, } from "./core/catalog.model.js";
|
|
8
|
+
export { deserializeCatalog, serializeCatalog, stringifyCatalogSnapshot, } from "./core/catalog.snapshot.js";
|
|
9
|
+
// Declarative schema export
|
|
10
|
+
export { exportDeclarativeSchema } from "./core/export/index.js";
|
|
11
|
+
// Plan operations
|
|
6
12
|
export { applyPlan } from "./core/plan/apply.js";
|
|
7
|
-
// Core operations
|
|
8
13
|
export { createPlan } from "./core/plan/create.js";
|
|
9
14
|
export { formatSqlStatements } from "./core/plan/sql-format.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supabase/pg-delta",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.6",
|
|
4
4
|
"description": "PostgreSQL migrations made easy",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -20,6 +20,20 @@
|
|
|
20
20
|
"require": "./dist/core/integrations/supabase.js",
|
|
21
21
|
"types": "./dist/core/integrations/supabase.d.ts",
|
|
22
22
|
"default": "./dist/core/integrations/supabase.js"
|
|
23
|
+
},
|
|
24
|
+
"./declarative": {
|
|
25
|
+
"bun": "./src/core/declarative-apply/index.ts",
|
|
26
|
+
"import": "./dist/core/declarative-apply/index.js",
|
|
27
|
+
"require": "./dist/core/declarative-apply/index.js",
|
|
28
|
+
"types": "./dist/core/declarative-apply/index.d.ts",
|
|
29
|
+
"default": "./dist/core/declarative-apply/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./catalog-export": {
|
|
32
|
+
"bun": "./src/core/catalog-export/index.ts",
|
|
33
|
+
"import": "./dist/core/catalog-export/index.js",
|
|
34
|
+
"require": "./dist/core/catalog-export/index.js",
|
|
35
|
+
"types": "./dist/core/catalog-export/index.d.ts",
|
|
36
|
+
"default": "./dist/core/catalog-export/index.js"
|
|
23
37
|
}
|
|
24
38
|
},
|
|
25
39
|
"bin": {
|
|
@@ -57,20 +71,23 @@
|
|
|
57
71
|
"format-and-lint": "biome check . --error-on-warnings",
|
|
58
72
|
"knip": "knip",
|
|
59
73
|
"pgdelta": "bun src/cli/bin/cli.ts",
|
|
60
|
-
"test": "bun
|
|
61
|
-
"test:unit": "bun test
|
|
62
|
-
"test:integration": "bun test
|
|
74
|
+
"test": "bun scripts/run-tests.ts",
|
|
75
|
+
"test:unit": "bun run test src/",
|
|
76
|
+
"test:integration": "bun run test tests/",
|
|
77
|
+
"update-empty-baseline": "bun scripts/update-empty-catalog-baseline.ts",
|
|
63
78
|
"version": "changeset version && bun install --no-frozen-lockfile && bun run format-and-lint --write"
|
|
64
79
|
},
|
|
65
80
|
"dependencies": {
|
|
66
81
|
"@stricli/core": "^1.2.4",
|
|
67
82
|
"@ts-safeql/sql-tag": "^0.2.0",
|
|
83
|
+
"@supabase/pg-topo": "^1.0.0-alpha.1",
|
|
68
84
|
"chalk": "^5.6.2",
|
|
69
85
|
"debug": "^4.3.7",
|
|
70
86
|
"pg": "^8.17.2",
|
|
71
87
|
"zod": "^4.2.1"
|
|
72
88
|
},
|
|
73
89
|
"devDependencies": {
|
|
90
|
+
"@supabase/bun-istanbul-coverage": "workspace:*",
|
|
74
91
|
"@tsconfig/node-ts": "^23.6.2",
|
|
75
92
|
"@tsconfig/node24": "^24.0.3",
|
|
76
93
|
"@types/bun": "^1.3.9",
|
package/src/cli/app.ts
CHANGED
|
@@ -1,13 +1,35 @@
|
|
|
1
1
|
import { buildApplication, buildRouteMap } from "@stricli/core";
|
|
2
2
|
import { applyCommand } from "./commands/apply.ts";
|
|
3
|
+
import { catalogExportCommand } from "./commands/catalog-export.ts";
|
|
4
|
+
import { declarativeApplyCommand } from "./commands/declarative-apply.ts";
|
|
5
|
+
import { declarativeExportCommand } from "./commands/declarative-export.ts";
|
|
3
6
|
import { planCommand } from "./commands/plan.ts";
|
|
4
7
|
import { syncCommand } from "./commands/sync.ts";
|
|
5
8
|
|
|
9
|
+
const declarativeRouteMap = buildRouteMap({
|
|
10
|
+
routes: {
|
|
11
|
+
apply: declarativeApplyCommand,
|
|
12
|
+
export: declarativeExportCommand,
|
|
13
|
+
},
|
|
14
|
+
docs: {
|
|
15
|
+
brief: "Declarative schema management",
|
|
16
|
+
fullDescription: `
|
|
17
|
+
Manage declarative SQL schemas.
|
|
18
|
+
|
|
19
|
+
Commands:
|
|
20
|
+
apply - Apply a declarative SQL schema to a database
|
|
21
|
+
export - Export a declarative schema from a database diff
|
|
22
|
+
`.trim(),
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
6
26
|
const root = buildRouteMap({
|
|
7
27
|
routes: {
|
|
8
28
|
plan: planCommand,
|
|
9
29
|
apply: applyCommand,
|
|
10
30
|
sync: syncCommand,
|
|
31
|
+
declarative: declarativeRouteMap,
|
|
32
|
+
"catalog-export": catalogExportCommand,
|
|
11
33
|
},
|
|
12
34
|
defaultCommand: "sync",
|
|
13
35
|
docs: {
|
|
@@ -16,9 +38,11 @@ const root = buildRouteMap({
|
|
|
16
38
|
pgdelta generates migration scripts by comparing two PostgreSQL databases.
|
|
17
39
|
|
|
18
40
|
Commands:
|
|
19
|
-
plan
|
|
20
|
-
apply
|
|
21
|
-
sync
|
|
41
|
+
plan - Compute schema diff and preview changes
|
|
42
|
+
apply - Apply a plan's migration script to a database
|
|
43
|
+
sync - Plan and apply changes in one go
|
|
44
|
+
declarative - Declarative schema (apply | export)
|
|
45
|
+
catalog-export - Export a database catalog as a snapshot JSON file
|
|
22
46
|
`.trim(),
|
|
23
47
|
},
|
|
24
48
|
});
|
package/src/cli/bin/cli.ts
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import { run } from "@stricli/core";
|
|
4
4
|
import { app } from "../app.ts";
|
|
5
|
+
import { getCommandExitCode } from "../exit-code.ts";
|
|
5
6
|
|
|
6
7
|
await run(app, process.argv.slice(2), { process }).catch((error) => {
|
|
7
8
|
console.error(error);
|
|
8
9
|
process.exit(1);
|
|
9
10
|
});
|
|
11
|
+
|
|
12
|
+
const code = getCommandExitCode();
|
|
13
|
+
if (code !== undefined) {
|
|
14
|
+
process.exitCode = code;
|
|
15
|
+
}
|