@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
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map changes to declarative schema file paths.
|
|
3
|
+
*/
|
|
4
|
+
import createDebug from "debug";
|
|
5
|
+
import { getObjectName, getObjectSchema, getParentInfo, } from "../plan/serialize.js";
|
|
6
|
+
const debugExport = createDebug("pg-delta:export");
|
|
7
|
+
function isRoleDefaultPrivilegeChange(change) {
|
|
8
|
+
return (change.objectType === "role" &&
|
|
9
|
+
change.scope === "default_privilege" &&
|
|
10
|
+
"inSchema" in change);
|
|
11
|
+
}
|
|
12
|
+
function requireSchema(change) {
|
|
13
|
+
const schema = getObjectSchema(change);
|
|
14
|
+
if (!schema) {
|
|
15
|
+
throw new Error(`Expected schema for ${change.objectType} change '${getObjectName(change)}' (operation: ${change.operation})`);
|
|
16
|
+
}
|
|
17
|
+
return schema;
|
|
18
|
+
}
|
|
19
|
+
function schemaPath(schema, ...parts) {
|
|
20
|
+
return `schemas/${schema}/${parts.join("/")}`;
|
|
21
|
+
}
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// File Path Mapping
|
|
24
|
+
// ============================================================================
|
|
25
|
+
export function getFilePath(change) {
|
|
26
|
+
switch (change.objectType) {
|
|
27
|
+
case "role":
|
|
28
|
+
if (isRoleDefaultPrivilegeChange(change) && change.inSchema) {
|
|
29
|
+
const schemaName = change.inSchema;
|
|
30
|
+
return {
|
|
31
|
+
path: schemaPath(schemaName, "schema.sql"),
|
|
32
|
+
category: "schema",
|
|
33
|
+
metadata: {
|
|
34
|
+
objectType: "default_privilege",
|
|
35
|
+
schemaName,
|
|
36
|
+
objectName: schemaName,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
path: "cluster/roles.sql",
|
|
42
|
+
category: "cluster",
|
|
43
|
+
metadata: { objectType: "role" },
|
|
44
|
+
};
|
|
45
|
+
case "extension": {
|
|
46
|
+
const extensionName = getObjectName(change);
|
|
47
|
+
return {
|
|
48
|
+
path: `cluster/extensions/${extensionName}.sql`,
|
|
49
|
+
category: "extensions",
|
|
50
|
+
metadata: { objectType: "extension", objectName: extensionName },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
case "foreign_data_wrapper":
|
|
54
|
+
case "server":
|
|
55
|
+
case "user_mapping":
|
|
56
|
+
return {
|
|
57
|
+
path: "cluster/foreign_data_wrappers.sql",
|
|
58
|
+
category: "cluster",
|
|
59
|
+
metadata: { objectType: change.objectType },
|
|
60
|
+
};
|
|
61
|
+
case "publication":
|
|
62
|
+
return {
|
|
63
|
+
path: "cluster/publications.sql",
|
|
64
|
+
category: "cluster",
|
|
65
|
+
metadata: { objectType: "publication" },
|
|
66
|
+
};
|
|
67
|
+
case "subscription":
|
|
68
|
+
return {
|
|
69
|
+
path: "cluster/subscriptions.sql",
|
|
70
|
+
category: "cluster",
|
|
71
|
+
metadata: { objectType: "subscription" },
|
|
72
|
+
};
|
|
73
|
+
case "event_trigger":
|
|
74
|
+
return {
|
|
75
|
+
path: "cluster/event_triggers.sql",
|
|
76
|
+
category: "cluster",
|
|
77
|
+
metadata: { objectType: "event_trigger" },
|
|
78
|
+
};
|
|
79
|
+
case "language":
|
|
80
|
+
return {
|
|
81
|
+
path: "cluster/languages.sql",
|
|
82
|
+
category: "cluster",
|
|
83
|
+
metadata: { objectType: "language" },
|
|
84
|
+
};
|
|
85
|
+
case "schema": {
|
|
86
|
+
const schemaName = change.schema.name;
|
|
87
|
+
return {
|
|
88
|
+
path: schemaPath(schemaName, "schema.sql"),
|
|
89
|
+
category: "schema",
|
|
90
|
+
metadata: {
|
|
91
|
+
objectType: "schema",
|
|
92
|
+
schemaName,
|
|
93
|
+
objectName: schemaName,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
case "enum":
|
|
98
|
+
case "composite_type":
|
|
99
|
+
case "range": {
|
|
100
|
+
const schema = requireSchema(change);
|
|
101
|
+
const objectName = getObjectName(change);
|
|
102
|
+
return {
|
|
103
|
+
path: schemaPath(schema, "types", `${objectName}.sql`),
|
|
104
|
+
category: "types",
|
|
105
|
+
metadata: {
|
|
106
|
+
objectType: change.objectType,
|
|
107
|
+
schemaName: schema,
|
|
108
|
+
objectName,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
case "domain": {
|
|
113
|
+
const schema = requireSchema(change);
|
|
114
|
+
const objectName = getObjectName(change);
|
|
115
|
+
return {
|
|
116
|
+
path: schemaPath(schema, "domains", `${objectName}.sql`),
|
|
117
|
+
category: "domains",
|
|
118
|
+
metadata: {
|
|
119
|
+
objectType: "domain",
|
|
120
|
+
schemaName: schema,
|
|
121
|
+
objectName,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
case "collation": {
|
|
126
|
+
const schema = requireSchema(change);
|
|
127
|
+
const objectName = getObjectName(change);
|
|
128
|
+
return {
|
|
129
|
+
path: schemaPath(schema, "collations", `${objectName}.sql`),
|
|
130
|
+
category: "collations",
|
|
131
|
+
metadata: {
|
|
132
|
+
objectType: "collation",
|
|
133
|
+
schemaName: schema,
|
|
134
|
+
objectName,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
case "sequence": {
|
|
139
|
+
const schema = requireSchema(change);
|
|
140
|
+
const objectName = getObjectName(change);
|
|
141
|
+
// ALTER SEQUENCE ... OWNED BY must be grouped with the owning table,
|
|
142
|
+
// not the sequence file, to avoid ordering issues: the table must exist
|
|
143
|
+
// before the OWNED BY clause can reference its column.
|
|
144
|
+
if (change.operation === "alter" &&
|
|
145
|
+
"ownedBy" in change &&
|
|
146
|
+
change.ownedBy) {
|
|
147
|
+
const ownedBy = change.ownedBy;
|
|
148
|
+
return {
|
|
149
|
+
path: schemaPath(ownedBy.schema, "tables", `${ownedBy.table}.sql`),
|
|
150
|
+
category: "tables",
|
|
151
|
+
metadata: {
|
|
152
|
+
objectType: "table",
|
|
153
|
+
schemaName: ownedBy.schema,
|
|
154
|
+
objectName: ownedBy.table,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
path: schemaPath(schema, "sequences", `${objectName}.sql`),
|
|
160
|
+
category: "sequences",
|
|
161
|
+
metadata: {
|
|
162
|
+
objectType: "sequence",
|
|
163
|
+
schemaName: schema,
|
|
164
|
+
objectName,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
case "table": {
|
|
169
|
+
const schema = change.table.schema;
|
|
170
|
+
const tableName = change.table.name;
|
|
171
|
+
// Partitions always go in the same file as their parent table.
|
|
172
|
+
if (change.table.is_partition && change.table.parent_name) {
|
|
173
|
+
const parentSchema = change.table.parent_schema ?? change.table.schema;
|
|
174
|
+
return {
|
|
175
|
+
path: schemaPath(parentSchema, "tables", `${change.table.parent_name}.sql`),
|
|
176
|
+
category: "tables",
|
|
177
|
+
metadata: {
|
|
178
|
+
objectType: "table",
|
|
179
|
+
schemaName: parentSchema,
|
|
180
|
+
objectName: change.table.parent_name,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
path: schemaPath(schema, "tables", `${tableName}.sql`),
|
|
186
|
+
category: "tables",
|
|
187
|
+
metadata: {
|
|
188
|
+
objectType: "table",
|
|
189
|
+
schemaName: schema,
|
|
190
|
+
objectName: tableName,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
case "foreign_table": {
|
|
195
|
+
const schema = requireSchema(change);
|
|
196
|
+
const objectName = getObjectName(change);
|
|
197
|
+
return {
|
|
198
|
+
path: schemaPath(schema, "foreign_tables", `${objectName}.sql`),
|
|
199
|
+
category: "foreign_tables",
|
|
200
|
+
metadata: {
|
|
201
|
+
objectType: "foreign_table",
|
|
202
|
+
schemaName: schema,
|
|
203
|
+
objectName,
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
case "view": {
|
|
208
|
+
const schema = requireSchema(change);
|
|
209
|
+
const objectName = getObjectName(change);
|
|
210
|
+
return {
|
|
211
|
+
path: schemaPath(schema, "views", `${objectName}.sql`),
|
|
212
|
+
category: "views",
|
|
213
|
+
metadata: {
|
|
214
|
+
objectType: "view",
|
|
215
|
+
schemaName: schema,
|
|
216
|
+
objectName,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
case "materialized_view": {
|
|
221
|
+
const schema = requireSchema(change);
|
|
222
|
+
const objectName = getObjectName(change);
|
|
223
|
+
return {
|
|
224
|
+
path: schemaPath(schema, "matviews", `${objectName}.sql`),
|
|
225
|
+
category: "matviews",
|
|
226
|
+
metadata: {
|
|
227
|
+
objectType: "materialized_view",
|
|
228
|
+
schemaName: schema,
|
|
229
|
+
objectName,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
case "procedure": {
|
|
234
|
+
const schema = requireSchema(change);
|
|
235
|
+
const objectName = getObjectName(change);
|
|
236
|
+
const isProcedure = change.procedure.kind === "p";
|
|
237
|
+
return {
|
|
238
|
+
path: schemaPath(schema, isProcedure ? "procedures" : "functions", `${objectName}.sql`),
|
|
239
|
+
category: isProcedure ? "procedures" : "functions",
|
|
240
|
+
metadata: {
|
|
241
|
+
objectType: isProcedure ? "procedure" : "function",
|
|
242
|
+
schemaName: schema,
|
|
243
|
+
objectName,
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
case "aggregate": {
|
|
248
|
+
const schema = requireSchema(change);
|
|
249
|
+
const objectName = getObjectName(change);
|
|
250
|
+
return {
|
|
251
|
+
path: schemaPath(schema, "aggregates", `${objectName}.sql`),
|
|
252
|
+
category: "aggregates",
|
|
253
|
+
metadata: {
|
|
254
|
+
objectType: "aggregate",
|
|
255
|
+
schemaName: schema,
|
|
256
|
+
objectName,
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
case "index": {
|
|
261
|
+
const schema = requireSchema(change);
|
|
262
|
+
const parent = getParentInfo(change);
|
|
263
|
+
if (!parent) {
|
|
264
|
+
throw new Error(`Expected parent for index '${getObjectName(change)}' in schema '${schema}'`);
|
|
265
|
+
}
|
|
266
|
+
const parentName = parent.name;
|
|
267
|
+
const category = parent.type === "materialized_view" ? "matviews" : "tables";
|
|
268
|
+
return {
|
|
269
|
+
path: schemaPath(schema, category, `${parentName}.sql`),
|
|
270
|
+
category,
|
|
271
|
+
metadata: {
|
|
272
|
+
objectType: parent.type,
|
|
273
|
+
schemaName: schema,
|
|
274
|
+
objectName: parentName,
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
case "trigger":
|
|
279
|
+
case "rls_policy":
|
|
280
|
+
case "rule": {
|
|
281
|
+
const schema = requireSchema(change);
|
|
282
|
+
const parent = getParentInfo(change);
|
|
283
|
+
if (!parent) {
|
|
284
|
+
throw new Error(`Expected parent for ${change.objectType} '${getObjectName(change)}' in schema '${schema}'`);
|
|
285
|
+
}
|
|
286
|
+
const parentName = parent.name;
|
|
287
|
+
const category = parent.type === "view"
|
|
288
|
+
? "views"
|
|
289
|
+
: parent.type === "materialized_view"
|
|
290
|
+
? "matviews"
|
|
291
|
+
: "tables";
|
|
292
|
+
return {
|
|
293
|
+
path: schemaPath(schema, category, `${parentName}.sql`),
|
|
294
|
+
category,
|
|
295
|
+
metadata: {
|
|
296
|
+
objectType: parent.type,
|
|
297
|
+
schemaName: schema,
|
|
298
|
+
objectName: parentName,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
default: {
|
|
303
|
+
const _exhaustive = change;
|
|
304
|
+
return _exhaustive;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Compile user-facing `GroupingPattern[]` into `CompiledPattern[]`.
|
|
310
|
+
* Strings are turned into `new RegExp(str)`. Invalid regex strings are skipped
|
|
311
|
+
* (no throw), so the returned `compiled` array may be shorter than the input.
|
|
312
|
+
* Any skipped patterns are reported in `warnings`.
|
|
313
|
+
*/
|
|
314
|
+
export function compilePatterns(patterns) {
|
|
315
|
+
const compiled = [];
|
|
316
|
+
const warnings = [];
|
|
317
|
+
for (const p of patterns) {
|
|
318
|
+
let regex;
|
|
319
|
+
if (typeof p.pattern === "string") {
|
|
320
|
+
try {
|
|
321
|
+
regex = new RegExp(p.pattern);
|
|
322
|
+
}
|
|
323
|
+
catch (e) {
|
|
324
|
+
const msg = `Skipping invalid grouping regex '${p.pattern}': ${e instanceof Error ? e.message : String(e)}`;
|
|
325
|
+
debugExport(msg);
|
|
326
|
+
warnings.push(msg);
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
// Strip /g and /y flags — .test() mutates lastIndex with these flags,
|
|
332
|
+
// causing non-deterministic matching across repeated calls.
|
|
333
|
+
const flags = p.pattern.flags.replace(/[gy]/g, "");
|
|
334
|
+
regex =
|
|
335
|
+
flags !== p.pattern.flags
|
|
336
|
+
? new RegExp(p.pattern.source, flags)
|
|
337
|
+
: p.pattern;
|
|
338
|
+
}
|
|
339
|
+
compiled.push({ regex, name: p.name });
|
|
340
|
+
}
|
|
341
|
+
return { compiled, warnings };
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Create a file mapper that applies regex-based grouping on top of the
|
|
345
|
+
* default `getFilePath` mapping.
|
|
346
|
+
*
|
|
347
|
+
* When no grouping config is provided (or it is undefined), the plain
|
|
348
|
+
* `getFilePath` function is returned unchanged.
|
|
349
|
+
*/
|
|
350
|
+
export function createFileMapper(grouping, onWarning) {
|
|
351
|
+
if (!grouping)
|
|
352
|
+
return getFilePath;
|
|
353
|
+
const { compiled, warnings } = compilePatterns(grouping.groupPatterns ?? []);
|
|
354
|
+
for (const w of warnings) {
|
|
355
|
+
onWarning?.(w);
|
|
356
|
+
}
|
|
357
|
+
const autoPartitions = grouping.autoGroupPartitions !== false; // default true
|
|
358
|
+
const flatSet = new Set(grouping.flatSchemas ?? []);
|
|
359
|
+
return (change) => {
|
|
360
|
+
const basePath = getFilePath(change);
|
|
361
|
+
// Flat schemas: collapse everything into one file per category.
|
|
362
|
+
// Applied first -- skips pattern matching for these schemas.
|
|
363
|
+
if (flatSet.size > 0 &&
|
|
364
|
+
basePath.metadata.schemaName &&
|
|
365
|
+
flatSet.has(basePath.metadata.schemaName)) {
|
|
366
|
+
return flattenSchema(basePath);
|
|
367
|
+
}
|
|
368
|
+
const groupName = resolveGroupName(change, basePath, compiled, autoPartitions);
|
|
369
|
+
if (!groupName)
|
|
370
|
+
return basePath;
|
|
371
|
+
return applyGrouping(basePath, groupName, grouping.mode);
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Flatten a schema-scoped file path into one file per category.
|
|
376
|
+
*
|
|
377
|
+
* e.g. `schemas/partman/tables/template_public_events.sql`
|
|
378
|
+
* → `schemas/partman/tables.sql`
|
|
379
|
+
*
|
|
380
|
+
* `schema.sql` is left unchanged (it is already flat).
|
|
381
|
+
*/
|
|
382
|
+
export function flattenSchema(filePath) {
|
|
383
|
+
const schema = filePath.metadata.schemaName ?? "";
|
|
384
|
+
const category = filePath.category;
|
|
385
|
+
// schema.sql stays as-is
|
|
386
|
+
if (category === "schema")
|
|
387
|
+
return filePath;
|
|
388
|
+
return {
|
|
389
|
+
path: schemaPath(schema, `${category}.sql`),
|
|
390
|
+
category,
|
|
391
|
+
metadata: {
|
|
392
|
+
...filePath.metadata,
|
|
393
|
+
objectName: category,
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Determine the group name for a change, or `null` if it should not be
|
|
399
|
+
* grouped.
|
|
400
|
+
*
|
|
401
|
+
* Resolution order:
|
|
402
|
+
* 1. Automatic partition detection -- resolve the parent table name.
|
|
403
|
+
* 2. Regex patterns -- first match wins (user controls priority by ordering).
|
|
404
|
+
*
|
|
405
|
+
* The resolved name from step 1 is fed through step 2 so that a partition
|
|
406
|
+
* parent name can itself be matched by a broader pattern (e.g. parent
|
|
407
|
+
* "kubernetes_resource_events" matches `/^kubernetes/`).
|
|
408
|
+
*
|
|
409
|
+
* If auto-detect resolved a parent but no pattern matched, the parent name
|
|
410
|
+
* is used as-is.
|
|
411
|
+
*/
|
|
412
|
+
export function resolveGroupName(change, filePath, patterns, autoPartitions) {
|
|
413
|
+
// Only schema-scoped objects can be grouped (skip cluster-level).
|
|
414
|
+
if (!filePath.metadata.schemaName)
|
|
415
|
+
return null;
|
|
416
|
+
// 1. Auto-detect partitions: table changes where the table is a partition
|
|
417
|
+
// of another table.
|
|
418
|
+
let resolvedName = null;
|
|
419
|
+
if (autoPartitions &&
|
|
420
|
+
change.objectType === "table" &&
|
|
421
|
+
change.table.is_partition &&
|
|
422
|
+
change.table.parent_name) {
|
|
423
|
+
resolvedName = change.table.parent_name;
|
|
424
|
+
}
|
|
425
|
+
// 2. Regex patterns -- first match wins.
|
|
426
|
+
const nameToMatch = resolvedName ?? filePath.metadata.objectName;
|
|
427
|
+
if (nameToMatch) {
|
|
428
|
+
for (const p of patterns) {
|
|
429
|
+
if (p.regex.test(nameToMatch)) {
|
|
430
|
+
return p.name;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
// 3. If auto-detect found a parent but no pattern matched, use the parent
|
|
435
|
+
// name directly.
|
|
436
|
+
return resolvedName;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Rewrite a `FilePath` according to the chosen grouping mode.
|
|
440
|
+
*
|
|
441
|
+
* - **single-file**: the filename becomes `{prefix}.sql` inside the original
|
|
442
|
+
* category directory.
|
|
443
|
+
* e.g. `schemas/public/tables/wal_verification_results_p20260107.sql`
|
|
444
|
+
* → `schemas/public/tables/wal_verification_results.sql`
|
|
445
|
+
*
|
|
446
|
+
* - **subdirectory**: the file is moved to a prefix-named directory under the
|
|
447
|
+
* schema root, with the category as the filename.
|
|
448
|
+
* e.g. `schemas/public/tables/wal_verification_results_p20260107.sql`
|
|
449
|
+
* → `schemas/public/wal_verification_results/tables.sql`
|
|
450
|
+
*/
|
|
451
|
+
export function applyGrouping(filePath, prefix, mode) {
|
|
452
|
+
const schema = filePath.metadata.schemaName ?? "";
|
|
453
|
+
const category = filePath.category;
|
|
454
|
+
if (mode === "single-file") {
|
|
455
|
+
// Replace the filename, keep the category directory.
|
|
456
|
+
return {
|
|
457
|
+
path: schemaPath(schema, category, `${prefix}.sql`),
|
|
458
|
+
category,
|
|
459
|
+
metadata: {
|
|
460
|
+
...filePath.metadata,
|
|
461
|
+
objectName: prefix,
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
// subdirectory mode: schemas/{schema}/{prefix}/{category}.sql
|
|
466
|
+
return {
|
|
467
|
+
path: schemaPath(schema, prefix, `${category}.sql`),
|
|
468
|
+
category,
|
|
469
|
+
metadata: {
|
|
470
|
+
...filePath.metadata,
|
|
471
|
+
objectName: prefix,
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group changes into declarative schema files and order them for readability.
|
|
3
|
+
*/
|
|
4
|
+
import type { Change } from "../change.types.ts";
|
|
5
|
+
import type { FileCategory, FileMetadata, FilePath } from "./types.ts";
|
|
6
|
+
interface FileGroup {
|
|
7
|
+
path: string;
|
|
8
|
+
category: FileCategory;
|
|
9
|
+
metadata: FileMetadata;
|
|
10
|
+
changes: Change[];
|
|
11
|
+
}
|
|
12
|
+
export declare function groupChangesByFile(changes: Change[], mapper?: (change: Change) => FilePath): FileGroup[];
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group changes into declarative schema files and order them for readability.
|
|
3
|
+
*/
|
|
4
|
+
import { getFilePath } from "./file-mapper.js";
|
|
5
|
+
import { CATEGORY_PRIORITY } from "./types.js";
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Within-file ordering
|
|
8
|
+
// ============================================================================
|
|
9
|
+
const OPERATION_PRIORITY = {
|
|
10
|
+
create: 0,
|
|
11
|
+
alter: 1,
|
|
12
|
+
};
|
|
13
|
+
const SCOPE_PRIORITY = {
|
|
14
|
+
object: 0,
|
|
15
|
+
comment: 1,
|
|
16
|
+
privilege: 2,
|
|
17
|
+
default_privilege: 3,
|
|
18
|
+
membership: 4,
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Sort changes within a file for readability:
|
|
22
|
+
* 1. By operation: create → alter
|
|
23
|
+
* 2. By scope: object → comment → privilege → default_privilege → membership
|
|
24
|
+
* 3. Stable tie-break by original position
|
|
25
|
+
*/
|
|
26
|
+
function sortChangesWithinFile(changes) {
|
|
27
|
+
// Tag each change with its original index for stable tie-breaking.
|
|
28
|
+
const tagged = changes.map((change, index) => ({ change, index }));
|
|
29
|
+
tagged.sort((a, b) => {
|
|
30
|
+
const opA = OPERATION_PRIORITY[a.change.operation] ?? 99;
|
|
31
|
+
const opB = OPERATION_PRIORITY[b.change.operation] ?? 99;
|
|
32
|
+
if (opA !== opB)
|
|
33
|
+
return opA - opB;
|
|
34
|
+
const scopeA = SCOPE_PRIORITY[a.change.scope ?? "object"] ?? 99;
|
|
35
|
+
const scopeB = SCOPE_PRIORITY[b.change.scope ?? "object"] ?? 99;
|
|
36
|
+
if (scopeA !== scopeB)
|
|
37
|
+
return scopeA - scopeB;
|
|
38
|
+
return a.index - b.index;
|
|
39
|
+
});
|
|
40
|
+
return tagged.map((t) => t.change);
|
|
41
|
+
}
|
|
42
|
+
// ============================================================================
|
|
43
|
+
// Grouping & Ordering
|
|
44
|
+
// ============================================================================
|
|
45
|
+
export function groupChangesByFile(changes, mapper = getFilePath) {
|
|
46
|
+
const groups = new Map();
|
|
47
|
+
for (const change of changes) {
|
|
48
|
+
const file = mapper(change);
|
|
49
|
+
const existing = groups.get(file.path);
|
|
50
|
+
if (!existing) {
|
|
51
|
+
groups.set(file.path, {
|
|
52
|
+
path: file.path,
|
|
53
|
+
category: file.category,
|
|
54
|
+
metadata: file.metadata,
|
|
55
|
+
changes: [change],
|
|
56
|
+
});
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
existing.changes.push(change);
|
|
60
|
+
}
|
|
61
|
+
// Sort within each file for readability.
|
|
62
|
+
for (const group of groups.values()) {
|
|
63
|
+
group.changes = sortChangesWithinFile(group.changes);
|
|
64
|
+
}
|
|
65
|
+
// Sort files by category priority, then alphabetically by path.
|
|
66
|
+
return Array.from(groups.values()).sort(sortByCategory);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Sort by category priority, then path for determinism.
|
|
70
|
+
*/
|
|
71
|
+
function sortByCategory(a, b) {
|
|
72
|
+
const categoryDiff = CATEGORY_PRIORITY[a.category] - CATEGORY_PRIORITY[b.category];
|
|
73
|
+
if (categoryDiff !== 0)
|
|
74
|
+
return categoryDiff;
|
|
75
|
+
return a.path.localeCompare(b.path);
|
|
76
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative schema export.
|
|
3
|
+
*/
|
|
4
|
+
import type { Integration } from "../integrations/integration.types.ts";
|
|
5
|
+
import type { createPlan } from "../plan/create.ts";
|
|
6
|
+
import type { SqlFormatOptions } from "../plan/sql-format/types.ts";
|
|
7
|
+
import type { DeclarativeSchemaOutput, Grouping } from "./types.ts";
|
|
8
|
+
/**
|
|
9
|
+
* The result of createPlan, containing the plan, sorted changes, and context.
|
|
10
|
+
* Use this type when you have already confirmed createPlan returned non-null.
|
|
11
|
+
*/
|
|
12
|
+
type PlanResult = NonNullable<Awaited<ReturnType<typeof createPlan>>>;
|
|
13
|
+
export interface ExportOptions {
|
|
14
|
+
/** Integration for custom serialization */
|
|
15
|
+
integration?: Integration;
|
|
16
|
+
/**
|
|
17
|
+
* SQL formatter options to control the output style.
|
|
18
|
+
* Merged on top of the default export options (maxWidth: 180, keywordCase: "upper").
|
|
19
|
+
* See `SqlFormatOptions` for available keys.
|
|
20
|
+
*/
|
|
21
|
+
formatOptions?: SqlFormatOptions | null;
|
|
22
|
+
/**
|
|
23
|
+
* Group entities by name prefix into consolidated files or subdirectories.
|
|
24
|
+
* Supports automatic partition detection and/or explicit prefix lists.
|
|
25
|
+
*/
|
|
26
|
+
grouping?: Grouping;
|
|
27
|
+
/** Callback for non-fatal warnings (e.g. invalid grouping regex patterns). */
|
|
28
|
+
onWarning?: (message: string) => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Export a declarative schema from a plan result.
|
|
32
|
+
*
|
|
33
|
+
* Takes the output of `createPlan()` and generates a declarative schema output
|
|
34
|
+
* with files grouped by object type. Drop operations are excluded since
|
|
35
|
+
* declarative mode targets the final desired state.
|
|
36
|
+
*
|
|
37
|
+
* Dependency-based filtering (cascading exclusions) is handled by `createPlan`,
|
|
38
|
+
* so this function only needs to filter out drop operations.
|
|
39
|
+
*
|
|
40
|
+
* @param planResult - The result from createPlan() containing plan, sortedChanges, and ctx
|
|
41
|
+
* @param options - Optional integration for custom serialization
|
|
42
|
+
* @returns Declarative schema output with grouped files
|
|
43
|
+
*/
|
|
44
|
+
export declare function exportDeclarativeSchema(planResult: PlanResult, options?: ExportOptions): DeclarativeSchemaOutput;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative schema export.
|
|
3
|
+
*/
|
|
4
|
+
import { buildPlanScopeFingerprint, hashStableIds } from "../fingerprint.js";
|
|
5
|
+
import { DEFAULT_OPTIONS } from "../plan/sql-format/constants.js";
|
|
6
|
+
import { formatSqlScript } from "../plan/statements.js";
|
|
7
|
+
import { createFileMapper } from "./file-mapper.js";
|
|
8
|
+
import { groupChangesByFile } from "./grouper.js";
|
|
9
|
+
/**
|
|
10
|
+
* Export a declarative schema from a plan result.
|
|
11
|
+
*
|
|
12
|
+
* Takes the output of `createPlan()` and generates a declarative schema output
|
|
13
|
+
* with files grouped by object type. Drop operations are excluded since
|
|
14
|
+
* declarative mode targets the final desired state.
|
|
15
|
+
*
|
|
16
|
+
* Dependency-based filtering (cascading exclusions) is handled by `createPlan`,
|
|
17
|
+
* so this function only needs to filter out drop operations.
|
|
18
|
+
*
|
|
19
|
+
* @param planResult - The result from createPlan() containing plan, sortedChanges, and ctx
|
|
20
|
+
* @param options - Optional integration for custom serialization
|
|
21
|
+
* @returns Declarative schema output with grouped files
|
|
22
|
+
*/
|
|
23
|
+
export function exportDeclarativeSchema(planResult, options) {
|
|
24
|
+
const { ctx, sortedChanges } = planResult;
|
|
25
|
+
const integration = options?.integration;
|
|
26
|
+
const formatOptions = options?.formatOptions === null
|
|
27
|
+
? undefined
|
|
28
|
+
: {
|
|
29
|
+
...DEFAULT_OPTIONS,
|
|
30
|
+
maxWidth: 180,
|
|
31
|
+
keywordCase: "upper",
|
|
32
|
+
...options?.formatOptions,
|
|
33
|
+
};
|
|
34
|
+
// Drop filtering and dependency cascading are handled upstream by createPlan.
|
|
35
|
+
const { hash: sourceFingerprint, stableIds } = buildPlanScopeFingerprint(ctx.mainCatalog, sortedChanges);
|
|
36
|
+
const targetFingerprint = hashStableIds(ctx.branchCatalog, stableIds);
|
|
37
|
+
const mapper = createFileMapper(options?.grouping, options?.onWarning);
|
|
38
|
+
const groups = groupChangesByFile(sortedChanges, mapper);
|
|
39
|
+
const files = groups.map((group, index) => {
|
|
40
|
+
const statements = group.changes.map((change) => serializeChange(change, integration));
|
|
41
|
+
return buildFileEntry(group.path, group.metadata, statements, index, formatOptions);
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
version: 1,
|
|
45
|
+
mode: "declarative",
|
|
46
|
+
generatedAt: new Date().toISOString(),
|
|
47
|
+
source: { fingerprint: sourceFingerprint },
|
|
48
|
+
target: { fingerprint: targetFingerprint },
|
|
49
|
+
files,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function serializeChange(change, integration) {
|
|
53
|
+
return integration?.serialize?.(change) ?? change.serialize();
|
|
54
|
+
}
|
|
55
|
+
function buildFileEntry(path, metadata, statements, order, formatOptions) {
|
|
56
|
+
return {
|
|
57
|
+
path,
|
|
58
|
+
order,
|
|
59
|
+
statements: statements.length,
|
|
60
|
+
sql: formatSqlScript(statements, formatOptions),
|
|
61
|
+
metadata,
|
|
62
|
+
};
|
|
63
|
+
}
|