supaschema 0.1.0-rc.1
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/.agents/skills/supaschema/SKILL.md +61 -0
- package/.claude/hooks/block-generated-migration-edits.mjs +32 -0
- package/.claude/rules/supaschema.md +22 -0
- package/.claude/settings.json +16 -0
- package/.claude/skills/supaschema/SKILL.md +61 -0
- package/.codex/hooks/supaschema-tool-gate.mjs +73 -0
- package/.codex/hooks.json +16 -0
- package/.codex/rules/supaschema.rules +22 -0
- package/AGENTS.md +40 -0
- package/LICENSE +661 -0
- package/LICENSE-COMMERCIAL.md +35 -0
- package/README.md +249 -0
- package/benchmarks/README.md +104 -0
- package/benchmarks/compare.js +489 -0
- package/benchmarks/fixtures/additive/from.sql +8 -0
- package/benchmarks/fixtures/additive/manifest.json +1 -0
- package/benchmarks/fixtures/additive/to.sql +9 -0
- package/benchmarks/fixtures/functions-policies/from.sql +24 -0
- package/benchmarks/fixtures/functions-policies/manifest.json +5 -0
- package/benchmarks/fixtures/functions-policies/to.sql +24 -0
- package/benchmarks/plot-lib.js +234 -0
- package/benchmarks/plot-svg.js +339 -0
- package/benchmarks/plot.js +154 -0
- package/benchmarks/tools/bench-all.sh +49 -0
- package/benchmarks/tools/build-project-fixture.mjs +245 -0
- package/benchmarks/tools/compare-db.mjs +101 -0
- package/benchmarks/tools/compare-fixtures.mjs +84 -0
- package/benchmarks/tools/compare-report.mjs +90 -0
- package/benchmarks/tools/compare-supabase.mjs +67 -0
- package/benchmarks/tools/registry.js +266 -0
- package/benchmarks/tools/run-workflow.mjs +77 -0
- package/bin/postinstall.mjs +26 -0
- package/bin/supaschema +2 -0
- package/config-schema.json +208 -0
- package/corpus/supabase-style/corpus.json +6 -0
- package/corpus/supabase-style/migrations/20260101000000_init.sql +28 -0
- package/corpus/supabase-style/migrations/20260102000000_noise.sql +13 -0
- package/corpus/supabase-style/migrations/20260103000000_churn.sql +4 -0
- package/corpus/supabase-style/migrations/20260104000000_triggers.sql +17 -0
- package/corpus/supabase-style/roles.sql +13 -0
- package/corpus/supabase-style/tree/functions.sql +26 -0
- package/corpus/supabase-style/tree/policies.sql +4 -0
- package/corpus/supabase-style/tree/schema.sql +4 -0
- package/corpus/supabase-style/tree/tables.sql +20 -0
- package/corpus/supabase-style/tree/triggers.sql +3 -0
- package/corpus/supabase-style/tree/types.sql +2 -0
- package/corpus/supabase-style/tree/views.sql +6 -0
- package/dist/audit.d.ts +20 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +68 -0
- package/dist/benchmark-db.d.ts +5 -0
- package/dist/benchmark-db.d.ts.map +1 -0
- package/dist/benchmark-db.js +71 -0
- package/dist/benchmark-fixtures.d.ts +10 -0
- package/dist/benchmark-fixtures.d.ts.map +1 -0
- package/dist/benchmark-fixtures.js +201 -0
- package/dist/benchmark.d.ts +2 -0
- package/dist/benchmark.d.ts.map +1 -0
- package/dist/benchmark.js +308 -0
- package/dist/catalog-comments.d.ts +9 -0
- package/dist/catalog-comments.d.ts.map +1 -0
- package/dist/catalog-comments.js +194 -0
- package/dist/catalog-extras.d.ts +12 -0
- package/dist/catalog-extras.d.ts.map +1 -0
- package/dist/catalog-extras.js +408 -0
- package/dist/catalog-foreign.d.ts +15 -0
- package/dist/catalog-foreign.d.ts.map +1 -0
- package/dist/catalog-foreign.js +114 -0
- package/dist/catalog-tables.d.ts +9 -0
- package/dist/catalog-tables.d.ts.map +1 -0
- package/dist/catalog-tables.js +114 -0
- package/dist/catalog.d.ts +8 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +351 -0
- package/dist/check-hazards.d.ts +7 -0
- package/dist/check-hazards.d.ts.map +1 -0
- package/dist/check-hazards.js +83 -0
- package/dist/check-reporters.d.ts +8 -0
- package/dist/check-reporters.d.ts.map +1 -0
- package/dist/check-reporters.js +76 -0
- package/dist/check.d.ts +3 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +229 -0
- package/dist/cli-defaults.d.ts +24 -0
- package/dist/cli-defaults.d.ts.map +1 -0
- package/dist/cli-defaults.js +65 -0
- package/dist/cli-diff.d.ts +13 -0
- package/dist/cli-diff.d.ts.map +1 -0
- package/dist/cli-diff.js +348 -0
- package/dist/cli-reports.d.ts +9 -0
- package/dist/cli-reports.d.ts.map +1 -0
- package/dist/cli-reports.js +90 -0
- package/dist/cli-tools.d.ts +17 -0
- package/dist/cli-tools.d.ts.map +1 -0
- package/dist/cli-tools.js +136 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +239 -0
- package/dist/config-schema-gen.d.ts +2 -0
- package/dist/config-schema-gen.d.ts.map +1 -0
- package/dist/config-schema-gen.js +11 -0
- package/dist/config.d.ts +58 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +132 -0
- package/dist/core.d.ts +115 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +1 -0
- package/dist/corpus.d.ts +26 -0
- package/dist/corpus.d.ts.map +1 -0
- package/dist/corpus.js +112 -0
- package/dist/database-url.d.ts +8 -0
- package/dist/database-url.d.ts.map +1 -0
- package/dist/database-url.js +74 -0
- package/dist/db-admin.d.ts +23 -0
- package/dist/db-admin.d.ts.map +1 -0
- package/dist/db-admin.js +147 -0
- package/dist/diagnostics.d.ts +16 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +155 -0
- package/dist/diff-score.d.ts +12 -0
- package/dist/diff-score.d.ts.map +1 -0
- package/dist/diff-score.js +339 -0
- package/dist/doctor.d.ts +17 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +110 -0
- package/dist/hash.d.ts +7 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +34 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/lineage.d.ts +23 -0
- package/dist/lineage.d.ts.map +1 -0
- package/dist/lineage.js +61 -0
- package/dist/migrations-status.d.ts +35 -0
- package/dist/migrations-status.d.ts.map +1 -0
- package/dist/migrations-status.js +131 -0
- package/dist/plan-order.d.ts +4 -0
- package/dist/plan-order.d.ts.map +1 -0
- package/dist/plan-order.js +178 -0
- package/dist/planner-replace.d.ts +4 -0
- package/dist/planner-replace.d.ts.map +1 -0
- package/dist/planner-replace.js +76 -0
- package/dist/planner-table.d.ts +3 -0
- package/dist/planner-table.d.ts.map +1 -0
- package/dist/planner-table.js +165 -0
- package/dist/planner.d.ts +5 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +385 -0
- package/dist/render-guards.d.ts +12 -0
- package/dist/render-guards.d.ts.map +1 -0
- package/dist/render-guards.js +159 -0
- package/dist/render.d.ts +7 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +325 -0
- package/dist/selfcheck.d.ts +11 -0
- package/dist/selfcheck.d.ts.map +1 -0
- package/dist/selfcheck.js +43 -0
- package/dist/source-normalize.d.ts +14 -0
- package/dist/source-normalize.d.ts.map +1 -0
- package/dist/source-normalize.js +420 -0
- package/dist/source.d.ts +4 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +233 -0
- package/dist/sql/ast.d.ts +42 -0
- package/dist/sql/ast.d.ts.map +1 -0
- package/dist/sql/ast.js +241 -0
- package/dist/sql/canonical-nodes.d.ts +5 -0
- package/dist/sql/canonical-nodes.d.ts.map +1 -0
- package/dist/sql/canonical-nodes.js +101 -0
- package/dist/sql/extract-helpers.d.ts +18 -0
- package/dist/sql/extract-helpers.d.ts.map +1 -0
- package/dist/sql/extract-helpers.js +127 -0
- package/dist/sql/extract.d.ts +13 -0
- package/dist/sql/extract.d.ts.map +1 -0
- package/dist/sql/extract.js +323 -0
- package/dist/sql/facts.d.ts +34 -0
- package/dist/sql/facts.d.ts.map +1 -0
- package/dist/sql/facts.js +392 -0
- package/dist/sql/identifiers.d.ts +13 -0
- package/dist/sql/identifiers.d.ts.map +1 -0
- package/dist/sql/identifiers.js +83 -0
- package/dist/sql/normalize-deparse.d.ts +25 -0
- package/dist/sql/normalize-deparse.d.ts.map +1 -0
- package/dist/sql/normalize-deparse.js +96 -0
- package/dist/sql/object-hash.d.ts +5 -0
- package/dist/sql/object-hash.d.ts.map +1 -0
- package/dist/sql/object-hash.js +24 -0
- package/dist/sql/parser.d.ts +8 -0
- package/dist/sql/parser.d.ts.map +1 -0
- package/dist/sql/parser.js +89 -0
- package/dist/sql/privileges.d.ts +33 -0
- package/dist/sql/privileges.d.ts.map +1 -0
- package/dist/sql/privileges.js +379 -0
- package/dist/sql/split.d.ts +3 -0
- package/dist/sql/split.d.ts.map +1 -0
- package/dist/sql/split.js +182 -0
- package/dist/sql/statements.d.ts +17 -0
- package/dist/sql/statements.d.ts.map +1 -0
- package/dist/sql/statements.js +284 -0
- package/dist/sql/table-constraints.d.ts +15 -0
- package/dist/sql/table-constraints.d.ts.map +1 -0
- package/dist/sql/table-constraints.js +304 -0
- package/dist/sql/table-shape.d.ts +38 -0
- package/dist/sql/table-shape.d.ts.map +1 -0
- package/dist/sql/table-shape.js +287 -0
- package/dist/sync.d.ts +27 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +86 -0
- package/dist/typegen-model.d.ts +78 -0
- package/dist/typegen-model.d.ts.map +1 -0
- package/dist/typegen-model.js +338 -0
- package/dist/typegen-views.d.ts +7 -0
- package/dist/typegen-views.d.ts.map +1 -0
- package/dist/typegen-views.js +92 -0
- package/dist/typegen-zod.d.ts +3 -0
- package/dist/typegen-zod.d.ts.map +1 -0
- package/dist/typegen-zod.js +149 -0
- package/dist/typegen.d.ts +4 -0
- package/dist/typegen.d.ts.map +1 -0
- package/dist/typegen.js +184 -0
- package/dist/validators.d.ts +3 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/validators.js +104 -0
- package/dist/verify-environment.d.ts +5 -0
- package/dist/verify-environment.d.ts.map +1 -0
- package/dist/verify-environment.js +92 -0
- package/dist/verify.d.ts +3 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +261 -0
- package/docs/benchmarks/additive-correctness.svg +86 -0
- package/docs/benchmarks/additive-latency.svg +60 -0
- package/docs/benchmarks/functions-policies-correctness.svg +86 -0
- package/docs/benchmarks/functions-policies-latency.svg +60 -0
- package/docs/benchmarks/realistic-correctness.svg +86 -0
- package/docs/benchmarks/realistic-latency.svg +60 -0
- package/docs/benchmarks/scaling-latency.svg +106 -0
- package/docs/benchmarks/workflow-latency.svg +98 -0
- package/docs/benchmarks/xl-correctness.svg +86 -0
- package/docs/benchmarks/xl-latency.svg +60 -0
- package/docs/benchmarks/xxl-correctness.svg +86 -0
- package/docs/benchmarks/xxl-latency.svg +66 -0
- package/docs/case-study-anilize.md +51 -0
- package/docs/ci-gate.md +44 -0
- package/docs/ci.md +68 -0
- package/docs/commands.md +35 -0
- package/docs/config.md +72 -0
- package/docs/corpus.md +33 -0
- package/docs/diagnostics.md +77 -0
- package/docs/hints.md +92 -0
- package/docs/release.md +19 -0
- package/docs/support-matrix.md +57 -0
- package/examples/postgres/schemas/001_app.sql +17 -0
- package/examples/supabase/schemas/001_app.sql +13 -0
- package/examples/supabase/schemas-next/001_app.sql +21 -0
- package/examples/supabase/supaschema.config.json +15 -0
- package/package.json +99 -0
package/dist/planner.js
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { resolveConfig } from "./config.js";
|
|
2
|
+
import { diagnostic } from "./diagnostics.js";
|
|
3
|
+
import { sha256, stableJson } from "./hash.js";
|
|
4
|
+
import { sortOperations } from "./plan-order.js";
|
|
5
|
+
import { isDestructiveAllowed, refineReplaceOperation } from "./planner-replace.js";
|
|
6
|
+
import { makeTableAlterOperation } from "./planner-table.js";
|
|
7
|
+
export function planSchemaDiff(from, to, options = {}) {
|
|
8
|
+
const config = resolveConfig(options.config);
|
|
9
|
+
const operations = [];
|
|
10
|
+
const diagnostics = [...from.diagnostics, ...to.diagnostics];
|
|
11
|
+
const fromMap = objectMap(from.objects);
|
|
12
|
+
const toMap = objectMap(to.objects);
|
|
13
|
+
const consumedFrom = new Set();
|
|
14
|
+
const consumedTo = new Set();
|
|
15
|
+
if (config.renameDetection === "hints-only") {
|
|
16
|
+
for (const hint of config.hints.renames ?? []) {
|
|
17
|
+
const before = fromMap.get(hint.from);
|
|
18
|
+
const after = toMap.get(hint.to);
|
|
19
|
+
if (!before || !after) {
|
|
20
|
+
diagnostics.push(diagnostic("SUPA_PLAN_RENAME_HINT_UNMATCHED", "error", "rename hint does not match both source and target objects", {
|
|
21
|
+
hint: `from=${hint.from} to=${hint.to}`,
|
|
22
|
+
}));
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const operation = makeRenameOperation(before, after);
|
|
26
|
+
operations.push(operation);
|
|
27
|
+
consumedFrom.add(before.key);
|
|
28
|
+
consumedTo.add(after.key);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
for (const [key, before] of fromMap) {
|
|
32
|
+
if (consumedFrom.has(key)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const after = toMap.get(key);
|
|
36
|
+
if (!after) {
|
|
37
|
+
operations.push(makeOperation("drop", key, before, undefined, config));
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (before.hash !== after.hash) {
|
|
41
|
+
operations.push(makeEnumAddValuesOperation(before, after) ??
|
|
42
|
+
makeTableAlterOperation(before, after, config) ??
|
|
43
|
+
refineReplaceOperation(makeOperation("replace", key, before, after, config), config));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (const [key, after] of toMap) {
|
|
47
|
+
if (consumedTo.has(key)) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (!fromMap.has(key)) {
|
|
51
|
+
operations.push(makeOperation("create", key, undefined, after, config));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
appendReplacedRelationDependents(operations, to, config);
|
|
55
|
+
const sortedOperations = sortOperations(operations, diagnostics);
|
|
56
|
+
for (const operation of operations) {
|
|
57
|
+
diagnostics.push(...operation.diagnostics);
|
|
58
|
+
}
|
|
59
|
+
if (sortedOperations.length === 0 && from.fingerprint !== to.fingerprint) {
|
|
60
|
+
diagnostics.push(emptyPlanDriftDiagnostic(fromMap, toMap, from, to));
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
diagnostics,
|
|
64
|
+
fingerprint: sha256(stableJson({
|
|
65
|
+
from: from.fingerprint,
|
|
66
|
+
operations: sortedOperations.map((operation) => ({
|
|
67
|
+
key: operation.key,
|
|
68
|
+
kind: operation.kind,
|
|
69
|
+
})),
|
|
70
|
+
to: to.fingerprint,
|
|
71
|
+
})),
|
|
72
|
+
from: from.source,
|
|
73
|
+
fromFingerprint: from.fingerprint,
|
|
74
|
+
operations: sortedOperations,
|
|
75
|
+
to: to.source,
|
|
76
|
+
toFingerprint: to.fingerprint,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function objectMap(objects) {
|
|
80
|
+
return new Map(objects.map((object) => [object.key, object]));
|
|
81
|
+
}
|
|
82
|
+
const replacedRelationKinds = new Set(["table", "materialized-view"]);
|
|
83
|
+
const relationDependentKinds = new Set([
|
|
84
|
+
"constraint",
|
|
85
|
+
"index",
|
|
86
|
+
"rls",
|
|
87
|
+
"policy",
|
|
88
|
+
"trigger",
|
|
89
|
+
]);
|
|
90
|
+
/**
|
|
91
|
+
* A relation replace renders DROP + CREATE, which destroys every dependent
|
|
92
|
+
* object in the target database even when that dependent is unchanged
|
|
93
|
+
* between the two models (equal hashes produce no operation). Re-create the
|
|
94
|
+
* to-state dependents alongside the replace so the rebuilt relation keeps
|
|
95
|
+
* its constraints, indexes, RLS state, policies, triggers, and grants.
|
|
96
|
+
*/
|
|
97
|
+
function appendReplacedRelationDependents(operations, to, config) {
|
|
98
|
+
const replacedRelations = operations
|
|
99
|
+
.filter((operation) => operation.kind === "replace" && replacedRelationKinds.has(operation.ref.kind))
|
|
100
|
+
.map((operation) => operation.ref);
|
|
101
|
+
if (replacedRelations.length === 0) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const operationKeys = new Set(operations.map((operation) => operation.key));
|
|
105
|
+
for (const relation of replacedRelations) {
|
|
106
|
+
for (const object of to.objects) {
|
|
107
|
+
if (operationKeys.has(object.key) || !isRelationDependent(object, relation)) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
operations.push(makeOperation("create", object.key, undefined, object, config));
|
|
111
|
+
operationKeys.add(object.key);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function isRelationDependent(object, relation) {
|
|
116
|
+
const schema = relation.schema ?? "public";
|
|
117
|
+
if (relationDependentKinds.has(object.ref.kind) &&
|
|
118
|
+
object.ref.table === relation.name &&
|
|
119
|
+
(object.ref.schema ?? "public") === schema) {
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
return (object.ref.kind === "grant" &&
|
|
123
|
+
typeof object.metadata.targetIdentity === "string" &&
|
|
124
|
+
object.metadata.targetIdentity === `${schema}.${relation.name}`);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* A diff engine's worst failure mode is an empty plan over states that
|
|
128
|
+
* actually differ. Zero operations must imply equal model fingerprints; when
|
|
129
|
+
* it does not, fail loud with the divergence instead of rendering a no-op.
|
|
130
|
+
*/
|
|
131
|
+
function emptyPlanDriftDiagnostic(fromMap, toMap, from, to) {
|
|
132
|
+
const differing = [];
|
|
133
|
+
for (const [key, before] of fromMap) {
|
|
134
|
+
const after = toMap.get(key);
|
|
135
|
+
if (!after) {
|
|
136
|
+
differing.push(`missing in target: ${key}`);
|
|
137
|
+
}
|
|
138
|
+
else if (before.hash !== after.hash) {
|
|
139
|
+
differing.push(`hash drift: ${key}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const key of toMap.keys()) {
|
|
143
|
+
if (!fromMap.has(key)) {
|
|
144
|
+
differing.push(`missing in source: ${key}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const sample = differing.slice(0, 12).join("; ");
|
|
148
|
+
return diagnostic("SUPA_PLAN_EMPTY_WITH_DRIFT", "error", "plan contains no operations but the model fingerprints differ", {
|
|
149
|
+
hint: differing.length > 0
|
|
150
|
+
? `${differing.length} differing object(s): ${sample}`
|
|
151
|
+
: `object sets are identical; fingerprint basis differs (from=${from.fingerprint} to=${to.fingerprint}) — check model format versions`,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function makeOperation(kind, key, before, after, config) {
|
|
155
|
+
const object = after ?? before;
|
|
156
|
+
if (!object) {
|
|
157
|
+
throw new Error(`operation ${kind} for ${key} has no object`);
|
|
158
|
+
}
|
|
159
|
+
const diagnostics = [];
|
|
160
|
+
const destructive = isDestructive(kind, object.ref.kind);
|
|
161
|
+
let blocked = false;
|
|
162
|
+
if (destructive && !isDestructiveAllowed(key, config)) {
|
|
163
|
+
blocked = true;
|
|
164
|
+
const difference = kind === "replace" ? describeReplaceDifference(before, after) : undefined;
|
|
165
|
+
diagnostics.push(diagnostic("SUPA_PLAN_DESTRUCTIVE_HINT_REQUIRED", "error", `${kind} of ${object.ref.kind} requires an explicit destructive-change hint${difference ? ` — ${difference}` : ""}`, {
|
|
166
|
+
hint: `Add "${key}" to hints.destructive only after reviewing the migration.`,
|
|
167
|
+
ref: object.ref,
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
if (kind === "replace" && object.ref.kind === "view") {
|
|
171
|
+
diagnostics.push(diagnostic("SUPA_PLAN_VIEW_REPLACE_VERIFY_REQUIRED", "warning", "PostgreSQL only permits CREATE OR REPLACE VIEW when the replacement shape is compatible", {
|
|
172
|
+
hint: "Run supaschema verify against a disposable PostgreSQL database before release.",
|
|
173
|
+
ref: object.ref,
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
if ((kind === "create" || kind === "replace") &&
|
|
177
|
+
object.ref.kind === "index" &&
|
|
178
|
+
object.metadata.concurrent === true &&
|
|
179
|
+
config.adapter === "supabase-auto") {
|
|
180
|
+
blocked = true;
|
|
181
|
+
diagnostics.push(diagnostic("SUPA_PLAN_CONCURRENT_INDEX_UNSUPPORTED", "error", "CREATE INDEX CONCURRENTLY cannot run inside the transaction Supabase db push uses", {
|
|
182
|
+
hint: "Create the index without CONCURRENTLY, or run it through an explicit out-of-transaction operational lane.",
|
|
183
|
+
ref: object.ref,
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
const operation = {
|
|
187
|
+
blocked,
|
|
188
|
+
destructive,
|
|
189
|
+
diagnostics,
|
|
190
|
+
key,
|
|
191
|
+
kind,
|
|
192
|
+
metadata: {},
|
|
193
|
+
ref: object.ref,
|
|
194
|
+
};
|
|
195
|
+
if (before) {
|
|
196
|
+
operation.before = before;
|
|
197
|
+
}
|
|
198
|
+
if (after) {
|
|
199
|
+
operation.after = after;
|
|
200
|
+
}
|
|
201
|
+
return operation;
|
|
202
|
+
}
|
|
203
|
+
function makeRenameOperation(before, after) {
|
|
204
|
+
const diagnostics = [];
|
|
205
|
+
let blocked = false;
|
|
206
|
+
if (before.ref.kind !== after.ref.kind) {
|
|
207
|
+
blocked = true;
|
|
208
|
+
diagnostics.push(diagnostic("SUPA_PLAN_RENAME_KIND_MISMATCH", "error", "rename hint changes object kind", {
|
|
209
|
+
hint: `${before.key} -> ${after.key}`,
|
|
210
|
+
ref: after.ref,
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
if (!isSupportedRenameKind(after.ref.kind)) {
|
|
214
|
+
blocked = true;
|
|
215
|
+
diagnostics.push(diagnostic("SUPA_PLAN_RENAME_UNSUPPORTED", "error", `${after.ref.kind} renames are not yet rendered safely`, {
|
|
216
|
+
hint: "Keep this change hand-authored or model it as an explicit create/drop with hints.",
|
|
217
|
+
ref: after.ref,
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
if (!sameRenameNamespace(before.ref, after.ref)) {
|
|
221
|
+
blocked = true;
|
|
222
|
+
diagnostics.push(diagnostic("SUPA_PLAN_RENAME_SET_SCHEMA_UNSUPPORTED", "error", "rename hints cannot move an object between schemas", {
|
|
223
|
+
hint: `${before.key} -> ${after.key}`,
|
|
224
|
+
ref: after.ref,
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
diagnostics.push(diagnostic("SUPA_PLAN_RENAME_VERIFY_REQUIRED", "warning", "explicit rename hints must be verified against a disposable PostgreSQL database", {
|
|
228
|
+
hint: `${before.key} -> ${after.key}`,
|
|
229
|
+
ref: after.ref,
|
|
230
|
+
}));
|
|
231
|
+
return {
|
|
232
|
+
after,
|
|
233
|
+
before,
|
|
234
|
+
blocked,
|
|
235
|
+
destructive: false,
|
|
236
|
+
diagnostics,
|
|
237
|
+
key: `${before.key}->${after.key}`,
|
|
238
|
+
kind: "rename",
|
|
239
|
+
metadata: {},
|
|
240
|
+
ref: after.ref,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function makeEnumAddValuesOperation(before, after) {
|
|
244
|
+
if (before.ref.kind !== "enum" || after.ref.kind !== "enum") {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
const beforeValues = enumValues(before);
|
|
248
|
+
const afterValues = enumValues(after);
|
|
249
|
+
if (!beforeValues || !afterValues || afterValues.length <= beforeValues.length) {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
const isPrefix = beforeValues.every((value, index) => afterValues[index] === value);
|
|
253
|
+
if (!isPrefix) {
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
after,
|
|
258
|
+
before,
|
|
259
|
+
blocked: false,
|
|
260
|
+
destructive: false,
|
|
261
|
+
diagnostics: [],
|
|
262
|
+
key: after.key,
|
|
263
|
+
kind: "alter",
|
|
264
|
+
metadata: { addEnumValues: afterValues.slice(beforeValues.length) },
|
|
265
|
+
ref: after.ref,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function enumValues(object) {
|
|
269
|
+
const values = object.metadata.values;
|
|
270
|
+
if (!Array.isArray(values)) {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
const strings = values.filter((value) => typeof value === "string");
|
|
274
|
+
return strings.length === values.length ? strings : undefined;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Names what actually differs between the two definitions so a gated replace
|
|
278
|
+
* is reviewable without manually diffing SQL. Tables get a per-column report
|
|
279
|
+
* from their canonical shapes; other kinds report a definition change.
|
|
280
|
+
*/
|
|
281
|
+
function describeReplaceDifference(before, after) {
|
|
282
|
+
if (!before || !after) {
|
|
283
|
+
return undefined;
|
|
284
|
+
}
|
|
285
|
+
const beforeShape = asShape(before.metadata.canonicalShape);
|
|
286
|
+
const afterShape = asShape(after.metadata.canonicalShape);
|
|
287
|
+
if (!beforeShape || !afterShape) {
|
|
288
|
+
return "definition differs";
|
|
289
|
+
}
|
|
290
|
+
const parts = [];
|
|
291
|
+
const beforeColumns = shapeColumns(beforeShape);
|
|
292
|
+
const afterColumns = shapeColumns(afterShape);
|
|
293
|
+
for (const [name, column] of beforeColumns) {
|
|
294
|
+
const other = afterColumns.get(name);
|
|
295
|
+
if (!other) {
|
|
296
|
+
parts.push(`column "${name}" only in current state`);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (stableJson(column) !== stableJson(other)) {
|
|
300
|
+
const changed = Object.keys({ ...column, ...other }).filter((field) => stableJson(column[field]) !== stableJson(other[field]));
|
|
301
|
+
parts.push(`column "${name}" differs (${changed.join(", ")})`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
for (const name of afterColumns.keys()) {
|
|
305
|
+
if (!beforeColumns.has(name)) {
|
|
306
|
+
parts.push(`column "${name}" only in target state`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const beforeRest = stableJson({ ...beforeShape, columns: undefined });
|
|
310
|
+
const afterRest = stableJson({ ...afterShape, columns: undefined });
|
|
311
|
+
if (beforeRest !== afterRest) {
|
|
312
|
+
const keys = new Set([...Object.keys(beforeShape), ...Object.keys(afterShape)]);
|
|
313
|
+
keys.delete("columns");
|
|
314
|
+
const changed = [...keys].filter((field) => stableJson(beforeShape[field]) !== stableJson(afterShape[field]));
|
|
315
|
+
parts.push(`table options differ (${changed.join(", ")})`);
|
|
316
|
+
}
|
|
317
|
+
return parts.length > 0 ? parts.join("; ") : "definition differs";
|
|
318
|
+
}
|
|
319
|
+
function asShape(value) {
|
|
320
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
321
|
+
? value
|
|
322
|
+
: undefined;
|
|
323
|
+
}
|
|
324
|
+
function shapeColumns(shape) {
|
|
325
|
+
const columns = Array.isArray(shape.columns) ? shape.columns : [];
|
|
326
|
+
const byName = new Map();
|
|
327
|
+
for (const column of columns) {
|
|
328
|
+
const record = asShape(column);
|
|
329
|
+
if (record && typeof record.name === "string") {
|
|
330
|
+
byName.set(record.name, record);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return byName;
|
|
334
|
+
}
|
|
335
|
+
function isDestructive(kind, objectKind) {
|
|
336
|
+
if (kind === "alter" || kind === "create" || kind === "rename") {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
if (kind === "drop") {
|
|
340
|
+
return [
|
|
341
|
+
"schema",
|
|
342
|
+
"table",
|
|
343
|
+
"foreign-data-wrapper",
|
|
344
|
+
"foreign-server",
|
|
345
|
+
"foreign-table",
|
|
346
|
+
"type",
|
|
347
|
+
"domain",
|
|
348
|
+
"enum",
|
|
349
|
+
"sequence",
|
|
350
|
+
"materialized-view",
|
|
351
|
+
"grant",
|
|
352
|
+
"default-privilege",
|
|
353
|
+
"rls",
|
|
354
|
+
].includes(objectKind);
|
|
355
|
+
}
|
|
356
|
+
return [
|
|
357
|
+
"table",
|
|
358
|
+
"foreign-data-wrapper",
|
|
359
|
+
"foreign-server",
|
|
360
|
+
"foreign-table",
|
|
361
|
+
"type",
|
|
362
|
+
"domain",
|
|
363
|
+
"enum",
|
|
364
|
+
"materialized-view",
|
|
365
|
+
"rls",
|
|
366
|
+
].includes(objectKind);
|
|
367
|
+
}
|
|
368
|
+
function isSupportedRenameKind(kind) {
|
|
369
|
+
return [
|
|
370
|
+
"schema",
|
|
371
|
+
"table",
|
|
372
|
+
"sequence",
|
|
373
|
+
"index",
|
|
374
|
+
"function",
|
|
375
|
+
"procedure",
|
|
376
|
+
"view",
|
|
377
|
+
"materialized-view",
|
|
378
|
+
].includes(kind);
|
|
379
|
+
}
|
|
380
|
+
function sameRenameNamespace(before, after) {
|
|
381
|
+
if (before.kind === "schema" && after.kind === "schema") {
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
return (before.schema ?? "public") === (after.schema ?? "public");
|
|
385
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MigrationOperation, ObjectRef, SchemaObject } from "./core.js";
|
|
2
|
+
export declare function ensureSemicolon(sql: string): string;
|
|
3
|
+
export declare function quoteLiteral(value: string): string;
|
|
4
|
+
export declare function qualifiedRef(ref: ObjectRef): string;
|
|
5
|
+
export declare function qualifiedTableRef(ref: ObjectRef): string;
|
|
6
|
+
export declare function renderRename(operation: MigrationOperation): string;
|
|
7
|
+
export declare function renderTypeGuard(object: SchemaObject): string;
|
|
8
|
+
export declare function renderFdwGuard(object: SchemaObject): string;
|
|
9
|
+
export declare function renderConstraintGuard(object: SchemaObject): string;
|
|
10
|
+
export declare function renderGrantDrop(object: SchemaObject): string;
|
|
11
|
+
export declare function renderDefaultPrivilegeDrop(object: SchemaObject): string;
|
|
12
|
+
//# sourceMappingURL=render-guards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-guards.d.ts","sourceRoot":"","sources":["../src/render-guards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAG7E,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAEnD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAExD;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,kBAAkB,GAAG,MAAM,CAkBlE;AAsCD,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAW5D;AAID,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAS3D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAmBlE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAiB5D;AAED,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAuBvE"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { formatQualifiedName, quoteIdent } from "./sql/identifiers.js";
|
|
2
|
+
export function ensureSemicolon(sql) {
|
|
3
|
+
const trimmed = sql.trim();
|
|
4
|
+
return trimmed.endsWith(";") ? trimmed : `${trimmed};`;
|
|
5
|
+
}
|
|
6
|
+
export function quoteLiteral(value) {
|
|
7
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
8
|
+
}
|
|
9
|
+
export function qualifiedRef(ref) {
|
|
10
|
+
return formatQualifiedName(ref.schema, ref.name);
|
|
11
|
+
}
|
|
12
|
+
export function qualifiedTableRef(ref) {
|
|
13
|
+
return formatQualifiedName(ref.schema, ref.table ?? ref.name);
|
|
14
|
+
}
|
|
15
|
+
export function renderRename(operation) {
|
|
16
|
+
const before = requiredBefore(operation);
|
|
17
|
+
const after = requiredAfter(operation);
|
|
18
|
+
const oldExists = existsExpression(before);
|
|
19
|
+
const newExists = existsExpression(after);
|
|
20
|
+
const renameSql = renderRenameStatement(before.ref, after.ref);
|
|
21
|
+
const conflict = quoteLiteral(`supaschema rename conflict: both ${before.key} and ${after.key} exist`);
|
|
22
|
+
return `DO $supaschema$
|
|
23
|
+
BEGIN
|
|
24
|
+
IF ${oldExists} AND ${newExists} THEN
|
|
25
|
+
RAISE EXCEPTION ${conflict};
|
|
26
|
+
ELSIF ${oldExists} THEN
|
|
27
|
+
${renameSql}
|
|
28
|
+
END IF;
|
|
29
|
+
END
|
|
30
|
+
$supaschema$;`;
|
|
31
|
+
}
|
|
32
|
+
function renderRenameStatement(before, after) {
|
|
33
|
+
switch (after.kind) {
|
|
34
|
+
case "schema":
|
|
35
|
+
return `ALTER SCHEMA ${quoteIdent(before.name)} RENAME TO ${quoteIdent(after.name)};`;
|
|
36
|
+
case "table":
|
|
37
|
+
return `ALTER TABLE ${qualifiedRef(before)} RENAME TO ${quoteIdent(after.name)};`;
|
|
38
|
+
case "sequence":
|
|
39
|
+
return `ALTER SEQUENCE ${qualifiedRef(before)} RENAME TO ${quoteIdent(after.name)};`;
|
|
40
|
+
case "index":
|
|
41
|
+
return `ALTER INDEX ${qualifiedRef(before)} RENAME TO ${quoteIdent(after.name)};`;
|
|
42
|
+
case "view":
|
|
43
|
+
return `ALTER VIEW ${qualifiedRef(before)} RENAME TO ${quoteIdent(after.name)};`;
|
|
44
|
+
case "materialized-view":
|
|
45
|
+
return `ALTER MATERIALIZED VIEW ${qualifiedRef(before)} RENAME TO ${quoteIdent(after.name)};`;
|
|
46
|
+
case "function":
|
|
47
|
+
return `ALTER FUNCTION ${qualifiedRef(before)}(${before.signature ?? ""}) RENAME TO ${quoteIdent(after.name)};`;
|
|
48
|
+
case "procedure":
|
|
49
|
+
return `ALTER PROCEDURE ${qualifiedRef(before)}(${before.signature ?? ""}) RENAME TO ${quoteIdent(after.name)};`;
|
|
50
|
+
default:
|
|
51
|
+
throw new Error(`unsupported rename operation for ${after.kind}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Guard bodies schema-qualify every catalog reference so a hostile or
|
|
55
|
+
// unusual search_path cannot redirect the existence check.
|
|
56
|
+
function existsExpression(object) {
|
|
57
|
+
const ref = object.ref;
|
|
58
|
+
if (ref.kind === "schema") {
|
|
59
|
+
return `EXISTS (SELECT 1 FROM pg_catalog.pg_namespace WHERE nspname = ${quoteLiteral(ref.name)})`;
|
|
60
|
+
}
|
|
61
|
+
if (ref.kind === "function" || ref.kind === "procedure") {
|
|
62
|
+
return `pg_catalog.to_regprocedure(${quoteLiteral(`${qualifiedRef(ref)}(${ref.signature ?? ""})`)}) IS NOT NULL`;
|
|
63
|
+
}
|
|
64
|
+
return `pg_catalog.to_regclass(${quoteLiteral(qualifiedRef(ref))}) IS NOT NULL`;
|
|
65
|
+
}
|
|
66
|
+
export function renderTypeGuard(object) {
|
|
67
|
+
const schema = object.ref.schema ?? "public";
|
|
68
|
+
const name = object.ref.name;
|
|
69
|
+
const catalogCheck = `SELECT 1 FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace WHERE n.nspname = ${quoteLiteral(schema)} AND t.typname = ${quoteLiteral(name)}`;
|
|
70
|
+
return `DO $supaschema$
|
|
71
|
+
BEGIN
|
|
72
|
+
IF NOT EXISTS (${catalogCheck}) THEN
|
|
73
|
+
${ensureSemicolon(object.sql)}
|
|
74
|
+
END IF;
|
|
75
|
+
END
|
|
76
|
+
$supaschema$;`;
|
|
77
|
+
}
|
|
78
|
+
// CREATE FOREIGN DATA WRAPPER has no IF NOT EXISTS form, so replay safety
|
|
79
|
+
// comes from a catalog-guarded DO block like types use.
|
|
80
|
+
export function renderFdwGuard(object) {
|
|
81
|
+
const catalogCheck = `SELECT 1 FROM pg_catalog.pg_foreign_data_wrapper WHERE fdwname = ${quoteLiteral(object.ref.name)}`;
|
|
82
|
+
return `DO $supaschema$
|
|
83
|
+
BEGIN
|
|
84
|
+
IF NOT EXISTS (${catalogCheck}) THEN
|
|
85
|
+
${ensureSemicolon(object.sql)}
|
|
86
|
+
END IF;
|
|
87
|
+
END
|
|
88
|
+
$supaschema$;`;
|
|
89
|
+
}
|
|
90
|
+
export function renderConstraintGuard(object) {
|
|
91
|
+
const schema = object.ref.schema ?? "public";
|
|
92
|
+
const table = object.ref.table ?? object.ref.name;
|
|
93
|
+
const name = object.ref.name;
|
|
94
|
+
return `DO $supaschema$
|
|
95
|
+
BEGIN
|
|
96
|
+
IF NOT EXISTS (
|
|
97
|
+
SELECT 1
|
|
98
|
+
FROM pg_catalog.pg_constraint c
|
|
99
|
+
JOIN pg_catalog.pg_class r ON r.oid = c.conrelid
|
|
100
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = r.relnamespace
|
|
101
|
+
WHERE n.nspname = ${quoteLiteral(schema)}
|
|
102
|
+
AND r.relname = ${quoteLiteral(table)}
|
|
103
|
+
AND c.conname = ${quoteLiteral(name)}
|
|
104
|
+
) THEN
|
|
105
|
+
${ensureSemicolon(object.sql)}
|
|
106
|
+
END IF;
|
|
107
|
+
END
|
|
108
|
+
$supaschema$;`;
|
|
109
|
+
}
|
|
110
|
+
export function renderGrantDrop(object) {
|
|
111
|
+
const verb = object.metadata.verb;
|
|
112
|
+
const privileges = object.metadata.privileges;
|
|
113
|
+
const kindPhrase = object.metadata.kindPhrase;
|
|
114
|
+
const target = object.metadata.target;
|
|
115
|
+
const grantee = object.metadata.grantee;
|
|
116
|
+
if (verb !== "GRANT" ||
|
|
117
|
+
!Array.isArray(privileges) ||
|
|
118
|
+
typeof kindPhrase !== "string" ||
|
|
119
|
+
typeof target !== "string" ||
|
|
120
|
+
typeof grantee !== "string") {
|
|
121
|
+
return `-- Manual privilege removal required for ${object.key}`;
|
|
122
|
+
}
|
|
123
|
+
const role = grantee === "PUBLIC" ? "PUBLIC" : quoteIdent(grantee);
|
|
124
|
+
return `REVOKE ${privileges.map(String).join(", ")} ON ${kindPhrase} ${target} FROM ${role};`;
|
|
125
|
+
}
|
|
126
|
+
export function renderDefaultPrivilegeDrop(object) {
|
|
127
|
+
const verb = object.metadata.verb;
|
|
128
|
+
const privileges = object.metadata.privileges;
|
|
129
|
+
const objectType = object.metadata.objectType;
|
|
130
|
+
const grantee = object.metadata.grantee;
|
|
131
|
+
if (verb !== "GRANT" ||
|
|
132
|
+
!Array.isArray(privileges) ||
|
|
133
|
+
typeof objectType !== "string" ||
|
|
134
|
+
typeof grantee !== "string") {
|
|
135
|
+
return `-- Manual privilege removal required for ${object.key}`;
|
|
136
|
+
}
|
|
137
|
+
const forRole = typeof object.metadata.forRole === "string" ? object.metadata.forRole : undefined;
|
|
138
|
+
const schema = typeof object.metadata.schema === "string" ? object.metadata.schema : undefined;
|
|
139
|
+
const role = grantee === "PUBLIC" ? "PUBLIC" : quoteIdent(grantee);
|
|
140
|
+
const clauses = [
|
|
141
|
+
"ALTER DEFAULT PRIVILEGES",
|
|
142
|
+
forRole ? `FOR ROLE ${quoteIdent(forRole)}` : "",
|
|
143
|
+
schema ? `IN SCHEMA ${quoteIdent(schema)}` : "",
|
|
144
|
+
`REVOKE ${privileges.map(String).join(", ")} ON ${objectType} FROM ${role}`,
|
|
145
|
+
].filter(Boolean);
|
|
146
|
+
return `${clauses.join(" ")};`;
|
|
147
|
+
}
|
|
148
|
+
function requiredBefore(operation) {
|
|
149
|
+
if (!operation.before) {
|
|
150
|
+
throw new Error(`operation ${operation.key} has no before object`);
|
|
151
|
+
}
|
|
152
|
+
return operation.before;
|
|
153
|
+
}
|
|
154
|
+
function requiredAfter(operation) {
|
|
155
|
+
if (!operation.after) {
|
|
156
|
+
throw new Error(`operation ${operation.key} has no after object`);
|
|
157
|
+
}
|
|
158
|
+
return operation.after;
|
|
159
|
+
}
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MigrationPlan, RenderOptions } from "./core.js";
|
|
2
|
+
export declare function renderMigrationSplit(plan: MigrationPlan, options?: RenderOptions): {
|
|
3
|
+
concurrentSql?: string;
|
|
4
|
+
sql: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function renderMigration(plan: MigrationPlan, options?: RenderOptions): string;
|
|
7
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,aAAa,EACb,aAAa,EAId,MAAM,WAAW,CAAC;AAgBnB,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,aAAa,EACnB,OAAO,GAAE,aAAkB,GAC1B;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAezC;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAcxF"}
|