@xubylele/schema-forge 1.12.1 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -5
- package/dist/api.d.ts +13 -1
- package/dist/api.js +1071 -166
- package/dist/cli.js +1083 -170
- package/package.json +12 -12
package/dist/api.js
CHANGED
|
@@ -30,11 +30,190 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
));
|
|
31
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
32
|
|
|
33
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/normalize.js
|
|
34
|
+
function normalizeIdent(input) {
|
|
35
|
+
return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
36
|
+
}
|
|
37
|
+
function pkName(table) {
|
|
38
|
+
return `pk_${normalizeIdent(table)}`;
|
|
39
|
+
}
|
|
40
|
+
function uqName(table, column) {
|
|
41
|
+
return `uq_${normalizeIdent(table)}_${normalizeIdent(column)}`;
|
|
42
|
+
}
|
|
43
|
+
function legacyPkName(table) {
|
|
44
|
+
return `${normalizeIdent(table)}_pkey`;
|
|
45
|
+
}
|
|
46
|
+
function legacyUqName(table, column) {
|
|
47
|
+
return `${normalizeIdent(table)}_${normalizeIdent(column)}_key`;
|
|
48
|
+
}
|
|
49
|
+
function simpleHash(input) {
|
|
50
|
+
let hash = 2166136261;
|
|
51
|
+
for (let index = 0; index < input.length; index++) {
|
|
52
|
+
hash ^= input.charCodeAt(index);
|
|
53
|
+
hash = Math.imul(hash, 16777619);
|
|
54
|
+
}
|
|
55
|
+
return (hash >>> 0).toString(36);
|
|
56
|
+
}
|
|
57
|
+
function hashSqlContent(value) {
|
|
58
|
+
return simpleHash(value);
|
|
59
|
+
}
|
|
60
|
+
function normalizeSqlExpression(value) {
|
|
61
|
+
if (!value) {
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
return normalizeSpacesOutsideQuotes(value);
|
|
65
|
+
}
|
|
66
|
+
function deterministicIndexName(params) {
|
|
67
|
+
const tableName = normalizeIdent(params.table) || "table";
|
|
68
|
+
const columnList = params.columns ?? [];
|
|
69
|
+
if (columnList.length > 0) {
|
|
70
|
+
const columnToken = columnList.map((column) => normalizeIdent(column) || "col").join("_").slice(0, 40);
|
|
71
|
+
return `idx_${tableName}_${columnToken}`;
|
|
72
|
+
}
|
|
73
|
+
const normalizedExpression = normalizeSqlExpression(params.expression);
|
|
74
|
+
const identExpr = normalizeIdent(normalizedExpression).slice(0, 24) || "expr";
|
|
75
|
+
const hash = simpleHash(normalizedExpression || params.expression || "").slice(0, 8);
|
|
76
|
+
return `idx_${tableName}_${identExpr}_${hash}`;
|
|
77
|
+
}
|
|
78
|
+
function normalizeSpacesOutsideQuotes(value) {
|
|
79
|
+
let result = "";
|
|
80
|
+
let inSingleQuote = false;
|
|
81
|
+
let inDoubleQuote = false;
|
|
82
|
+
let pendingSpace = false;
|
|
83
|
+
for (const char of value) {
|
|
84
|
+
if (char === "'" && !inDoubleQuote) {
|
|
85
|
+
if (pendingSpace && result.length > 0 && result[result.length - 1] !== " ") {
|
|
86
|
+
result += " ";
|
|
87
|
+
}
|
|
88
|
+
pendingSpace = false;
|
|
89
|
+
inSingleQuote = !inSingleQuote;
|
|
90
|
+
result += char;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (char === '"' && !inSingleQuote) {
|
|
94
|
+
if (pendingSpace && result.length > 0 && result[result.length - 1] !== " ") {
|
|
95
|
+
result += " ";
|
|
96
|
+
}
|
|
97
|
+
pendingSpace = false;
|
|
98
|
+
inDoubleQuote = !inDoubleQuote;
|
|
99
|
+
result += char;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!inSingleQuote && !inDoubleQuote && /\s/.test(char)) {
|
|
103
|
+
pendingSpace = true;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (pendingSpace && result.length > 0 && result[result.length - 1] !== " ") {
|
|
107
|
+
result += " ";
|
|
108
|
+
}
|
|
109
|
+
pendingSpace = false;
|
|
110
|
+
result += char;
|
|
111
|
+
}
|
|
112
|
+
return result.trim();
|
|
113
|
+
}
|
|
114
|
+
function normalizeKnownFunctionsOutsideQuotes(value) {
|
|
115
|
+
let result = "";
|
|
116
|
+
let inSingleQuote = false;
|
|
117
|
+
let inDoubleQuote = false;
|
|
118
|
+
let buffer = "";
|
|
119
|
+
function flushBuffer() {
|
|
120
|
+
if (!buffer) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
result += buffer.replace(/\bnow\s*\(\s*\)/gi, "now()").replace(/\bgen_random_uuid\s*\(\s*\)/gi, "gen_random_uuid()");
|
|
124
|
+
buffer = "";
|
|
125
|
+
}
|
|
126
|
+
for (const char of value) {
|
|
127
|
+
if (char === "'" && !inDoubleQuote) {
|
|
128
|
+
flushBuffer();
|
|
129
|
+
inSingleQuote = !inSingleQuote;
|
|
130
|
+
result += char;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (char === '"' && !inSingleQuote) {
|
|
134
|
+
flushBuffer();
|
|
135
|
+
inDoubleQuote = !inDoubleQuote;
|
|
136
|
+
result += char;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (inSingleQuote || inDoubleQuote) {
|
|
140
|
+
result += char;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
buffer += char;
|
|
144
|
+
}
|
|
145
|
+
flushBuffer();
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
function normalizePunctuationOutsideQuotes(value) {
|
|
149
|
+
let result = "";
|
|
150
|
+
let inSingleQuote = false;
|
|
151
|
+
let inDoubleQuote = false;
|
|
152
|
+
for (let index = 0; index < value.length; index++) {
|
|
153
|
+
const char = value[index];
|
|
154
|
+
if (char === "'" && !inDoubleQuote) {
|
|
155
|
+
inSingleQuote = !inSingleQuote;
|
|
156
|
+
result += char;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (char === '"' && !inSingleQuote) {
|
|
160
|
+
inDoubleQuote = !inDoubleQuote;
|
|
161
|
+
result += char;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (!inSingleQuote && !inDoubleQuote && (char === "(" || char === ")")) {
|
|
165
|
+
while (result.endsWith(" ")) {
|
|
166
|
+
result = result.slice(0, -1);
|
|
167
|
+
}
|
|
168
|
+
result += char;
|
|
169
|
+
let lookahead = index + 1;
|
|
170
|
+
while (lookahead < value.length && value[lookahead] === " ") {
|
|
171
|
+
lookahead++;
|
|
172
|
+
}
|
|
173
|
+
index = lookahead - 1;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (!inSingleQuote && !inDoubleQuote && char === ",") {
|
|
177
|
+
while (result.endsWith(" ")) {
|
|
178
|
+
result = result.slice(0, -1);
|
|
179
|
+
}
|
|
180
|
+
result += ", ";
|
|
181
|
+
let lookahead = index + 1;
|
|
182
|
+
while (lookahead < value.length && value[lookahead] === " ") {
|
|
183
|
+
lookahead++;
|
|
184
|
+
}
|
|
185
|
+
index = lookahead - 1;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
result += char;
|
|
189
|
+
}
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
function normalizeDefault(expr) {
|
|
193
|
+
if (expr === void 0 || expr === null) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
const trimmed = expr.trim();
|
|
197
|
+
if (trimmed.length === 0) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
const normalizedSpacing = normalizeSpacesOutsideQuotes(trimmed);
|
|
201
|
+
const normalizedPunctuation = normalizePunctuationOutsideQuotes(normalizedSpacing);
|
|
202
|
+
return normalizeKnownFunctionsOutsideQuotes(normalizedPunctuation);
|
|
203
|
+
}
|
|
204
|
+
var init_normalize = __esm({
|
|
205
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/normalize.js"() {
|
|
206
|
+
"use strict";
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
33
210
|
// node_modules/@xubylele/schema-forge-core/dist/core/parser.js
|
|
34
211
|
function parseSchema(source) {
|
|
35
212
|
const lines = source.split("\n");
|
|
36
213
|
const tables = {};
|
|
37
214
|
const policyList = [];
|
|
215
|
+
const indexList = [];
|
|
216
|
+
const viewList = [];
|
|
38
217
|
let currentLine = 0;
|
|
39
218
|
const validBaseColumnTypes = /* @__PURE__ */ new Set([
|
|
40
219
|
"uuid",
|
|
@@ -338,6 +517,48 @@ function parseSchema(source) {
|
|
|
338
517
|
};
|
|
339
518
|
return { policy, nextLineIndex: lineIdx };
|
|
340
519
|
}
|
|
520
|
+
const policyDeclRegex = /^policy\s+"([^"]*)"\s+on\s+\w+/;
|
|
521
|
+
function parseViewDeclaration(startLine, options) {
|
|
522
|
+
const firstLine = cleanLine(lines[startLine]);
|
|
523
|
+
const declMatch = firstLine.match(/^view\s+(\w+)\s+as(?:\s+([\s\S]+))?$/);
|
|
524
|
+
if (!declMatch) {
|
|
525
|
+
throw new Error(`Line ${startLine + 1}: Invalid view declaration. Expected: view <name> as <sql>`);
|
|
526
|
+
}
|
|
527
|
+
const viewName = declMatch[1];
|
|
528
|
+
validateIdentifier(viewName, startLine + 1, "view");
|
|
529
|
+
const queryLines = [];
|
|
530
|
+
const inlineQuery = declMatch[2]?.trim();
|
|
531
|
+
if (inlineQuery) {
|
|
532
|
+
queryLines.push(inlineQuery);
|
|
533
|
+
}
|
|
534
|
+
let lineIdx = startLine + 1;
|
|
535
|
+
while (lineIdx < lines.length) {
|
|
536
|
+
const rawLine = lines[lineIdx];
|
|
537
|
+
const cleaned = cleanLine(rawLine);
|
|
538
|
+
if (!cleaned) {
|
|
539
|
+
if (queryLines.length > 0) {
|
|
540
|
+
queryLines.push("");
|
|
541
|
+
}
|
|
542
|
+
lineIdx++;
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
if (cleaned.startsWith("table ") || policyDeclRegex.test(cleaned) || cleaned.startsWith("index ") || cleaned.startsWith("view ") || options?.stopAtTableClose === true && cleaned === "}") {
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
queryLines.push(rawLine.trim());
|
|
549
|
+
lineIdx++;
|
|
550
|
+
}
|
|
551
|
+
const query = queryLines.join("\n").trim();
|
|
552
|
+
if (!query) {
|
|
553
|
+
throw new Error(`Line ${startLine + 1}: View '${viewName}' is missing SQL query body`);
|
|
554
|
+
}
|
|
555
|
+
const view = {
|
|
556
|
+
name: viewName,
|
|
557
|
+
query,
|
|
558
|
+
hash: hashSqlContent(query)
|
|
559
|
+
};
|
|
560
|
+
return { view, nextLineIndex: lineIdx };
|
|
561
|
+
}
|
|
341
562
|
function parseTableBlock(startLine) {
|
|
342
563
|
const firstLine = cleanLine(lines[startLine]);
|
|
343
564
|
const match = firstLine.match(/^table\s+(\w+)\s*\{\s*$/);
|
|
@@ -362,6 +583,12 @@ function parseSchema(source) {
|
|
|
362
583
|
foundClosingBrace = true;
|
|
363
584
|
break;
|
|
364
585
|
}
|
|
586
|
+
if (cleaned.startsWith("view ")) {
|
|
587
|
+
const { view, nextLineIndex } = parseViewDeclaration(lineIdx, { stopAtTableClose: true });
|
|
588
|
+
viewList.push({ view, startLine: lineIdx + 1 });
|
|
589
|
+
lineIdx = nextLineIndex;
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
365
592
|
try {
|
|
366
593
|
const column = parseColumn(cleaned, lineIdx + 1);
|
|
367
594
|
columns.push(column);
|
|
@@ -381,7 +608,129 @@ function parseSchema(source) {
|
|
|
381
608
|
};
|
|
382
609
|
return lineIdx;
|
|
383
610
|
}
|
|
384
|
-
|
|
611
|
+
function parseIndexColumns(columnsRaw, lineNum) {
|
|
612
|
+
const parsed = columnsRaw.split(",").map((column) => column.trim()).filter(Boolean);
|
|
613
|
+
if (parsed.length === 0) {
|
|
614
|
+
throw new Error(`Line ${lineNum}: Index columns clause requires at least one column`);
|
|
615
|
+
}
|
|
616
|
+
for (const column of parsed) {
|
|
617
|
+
validateIdentifier(column, lineNum, "column");
|
|
618
|
+
}
|
|
619
|
+
return parsed;
|
|
620
|
+
}
|
|
621
|
+
function parseIndexDeclaration(startLine) {
|
|
622
|
+
const firstLine = cleanLine(lines[startLine]);
|
|
623
|
+
const inlineExprMatch = firstLine.match(/^index(?:\s+(\w+))?\s+on\s+(\w+)\s*\((.+)\)\s*$/);
|
|
624
|
+
if (inlineExprMatch) {
|
|
625
|
+
const name = inlineExprMatch[1];
|
|
626
|
+
const table2 = inlineExprMatch[2];
|
|
627
|
+
const expressionRaw = inlineExprMatch[3].trim();
|
|
628
|
+
if (name) {
|
|
629
|
+
validateIdentifier(name, startLine + 1, "column");
|
|
630
|
+
}
|
|
631
|
+
validateIdentifier(table2, startLine + 1, "table");
|
|
632
|
+
if (!expressionRaw) {
|
|
633
|
+
throw new Error(`Line ${startLine + 1}: Expression index requires a non-empty expression`);
|
|
634
|
+
}
|
|
635
|
+
const normalizedExpression = normalizeSqlExpression(expressionRaw);
|
|
636
|
+
const resolvedName2 = name ?? deterministicIndexName({ table: table2, expression: normalizedExpression });
|
|
637
|
+
return {
|
|
638
|
+
index: {
|
|
639
|
+
name: resolvedName2,
|
|
640
|
+
table: table2,
|
|
641
|
+
columns: [],
|
|
642
|
+
unique: false,
|
|
643
|
+
expression: normalizedExpression
|
|
644
|
+
},
|
|
645
|
+
nextLineIndex: startLine + 1
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
const blockMatch = firstLine.match(/^index(?:\s+(\w+))?\s+on\s+(\w+)\s*$/);
|
|
649
|
+
if (!blockMatch) {
|
|
650
|
+
throw new Error(`Line ${startLine + 1}: Invalid index declaration. Expected: index [name] on <table> or index [name] on <table>(<expression>)`);
|
|
651
|
+
}
|
|
652
|
+
const declaredName = blockMatch[1];
|
|
653
|
+
const table = blockMatch[2];
|
|
654
|
+
if (declaredName) {
|
|
655
|
+
validateIdentifier(declaredName, startLine + 1, "column");
|
|
656
|
+
}
|
|
657
|
+
validateIdentifier(table, startLine + 1, "table");
|
|
658
|
+
let lineIdx = startLine + 1;
|
|
659
|
+
let columns;
|
|
660
|
+
let unique = false;
|
|
661
|
+
let where;
|
|
662
|
+
let expression;
|
|
663
|
+
const seenClauses = /* @__PURE__ */ new Set();
|
|
664
|
+
while (lineIdx < lines.length) {
|
|
665
|
+
const cleaned = cleanLine(lines[lineIdx]);
|
|
666
|
+
if (!cleaned) {
|
|
667
|
+
lineIdx++;
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
if (cleaned.startsWith("table ") || cleaned.startsWith("policy ") || cleaned.startsWith("index ")) {
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
if (cleaned === "unique") {
|
|
674
|
+
if (seenClauses.has("unique")) {
|
|
675
|
+
throw new Error(`Line ${lineIdx + 1}: Duplicate 'unique' in index declaration`);
|
|
676
|
+
}
|
|
677
|
+
seenClauses.add("unique");
|
|
678
|
+
unique = true;
|
|
679
|
+
lineIdx++;
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
if (cleaned.startsWith("columns ")) {
|
|
683
|
+
if (seenClauses.has("columns")) {
|
|
684
|
+
throw new Error(`Line ${lineIdx + 1}: Duplicate 'columns' in index declaration`);
|
|
685
|
+
}
|
|
686
|
+
seenClauses.add("columns");
|
|
687
|
+
columns = parseIndexColumns(cleaned.slice("columns ".length), lineIdx + 1);
|
|
688
|
+
lineIdx++;
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
if (cleaned.startsWith("where ")) {
|
|
692
|
+
if (seenClauses.has("where")) {
|
|
693
|
+
throw new Error(`Line ${lineIdx + 1}: Duplicate 'where' in index declaration`);
|
|
694
|
+
}
|
|
695
|
+
seenClauses.add("where");
|
|
696
|
+
where = normalizeSqlExpression(cleaned.slice("where ".length));
|
|
697
|
+
lineIdx++;
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
if (cleaned.startsWith("expression ")) {
|
|
701
|
+
if (seenClauses.has("expression")) {
|
|
702
|
+
throw new Error(`Line ${lineIdx + 1}: Duplicate 'expression' in index declaration`);
|
|
703
|
+
}
|
|
704
|
+
seenClauses.add("expression");
|
|
705
|
+
expression = normalizeSqlExpression(cleaned.slice("expression ".length));
|
|
706
|
+
lineIdx++;
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
throw new Error(`Line ${lineIdx + 1}: Unexpected index clause '${cleaned}'`);
|
|
710
|
+
}
|
|
711
|
+
if (columns && expression || !columns && !expression) {
|
|
712
|
+
throw new Error(`Line ${startLine + 1}: Index must define exactly one target kind: either 'columns' or 'expression'`);
|
|
713
|
+
}
|
|
714
|
+
if (expression !== void 0 && expression.trim().length === 0) {
|
|
715
|
+
throw new Error(`Line ${startLine + 1}: Expression index requires a non-empty expression`);
|
|
716
|
+
}
|
|
717
|
+
const resolvedName = declaredName ?? deterministicIndexName({
|
|
718
|
+
table,
|
|
719
|
+
columns,
|
|
720
|
+
expression
|
|
721
|
+
});
|
|
722
|
+
return {
|
|
723
|
+
index: {
|
|
724
|
+
name: resolvedName,
|
|
725
|
+
table,
|
|
726
|
+
columns: columns ?? [],
|
|
727
|
+
unique,
|
|
728
|
+
...where !== void 0 && { where },
|
|
729
|
+
...expression !== void 0 && { expression }
|
|
730
|
+
},
|
|
731
|
+
nextLineIndex: lineIdx
|
|
732
|
+
};
|
|
733
|
+
}
|
|
385
734
|
while (currentLine < lines.length) {
|
|
386
735
|
const cleaned = cleanLine(lines[currentLine]);
|
|
387
736
|
if (!cleaned) {
|
|
@@ -395,9 +744,26 @@ function parseSchema(source) {
|
|
|
395
744
|
const { policy, nextLineIndex } = parsePolicyBlock(currentLine);
|
|
396
745
|
policyList.push({ policy, startLine: currentLine + 1 });
|
|
397
746
|
currentLine = nextLineIndex;
|
|
747
|
+
} else if (cleaned.startsWith("index ")) {
|
|
748
|
+
const { index, nextLineIndex } = parseIndexDeclaration(currentLine);
|
|
749
|
+
indexList.push({ index, startLine: currentLine + 1 });
|
|
750
|
+
currentLine = nextLineIndex;
|
|
751
|
+
} else if (cleaned.startsWith("view ")) {
|
|
752
|
+
const { view, nextLineIndex } = parseViewDeclaration(currentLine);
|
|
753
|
+
viewList.push({ view, startLine: currentLine + 1 });
|
|
754
|
+
currentLine = nextLineIndex;
|
|
398
755
|
} else {
|
|
399
|
-
throw new Error(`Line ${currentLine + 1}: Unexpected content '${cleaned}'. Expected table or policy definition.`);
|
|
756
|
+
throw new Error(`Line ${currentLine + 1}: Unexpected content '${cleaned}'. Expected table, index, view, or policy definition.`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
for (const { index, startLine } of indexList) {
|
|
760
|
+
if (!tables[index.table]) {
|
|
761
|
+
throw new Error(`Line ${startLine}: Index "${index.name}" references undefined table "${index.table}"`);
|
|
762
|
+
}
|
|
763
|
+
if (!tables[index.table].indexes) {
|
|
764
|
+
tables[index.table].indexes = [];
|
|
400
765
|
}
|
|
766
|
+
tables[index.table].indexes.push(index);
|
|
401
767
|
}
|
|
402
768
|
for (const { policy, startLine } of policyList) {
|
|
403
769
|
if (!tables[policy.table]) {
|
|
@@ -408,164 +774,27 @@ function parseSchema(source) {
|
|
|
408
774
|
}
|
|
409
775
|
tables[policy.table].policies.push(policy);
|
|
410
776
|
}
|
|
411
|
-
|
|
777
|
+
const views = {};
|
|
778
|
+
for (const { view, startLine } of viewList) {
|
|
779
|
+
if (views[view.name]) {
|
|
780
|
+
throw new Error(`Line ${startLine}: Duplicate view definition '${view.name}'`);
|
|
781
|
+
}
|
|
782
|
+
views[view.name] = view;
|
|
783
|
+
}
|
|
784
|
+
return {
|
|
785
|
+
tables,
|
|
786
|
+
...Object.keys(views).length > 0 && { views }
|
|
787
|
+
};
|
|
412
788
|
}
|
|
413
789
|
var POLICY_COMMANDS;
|
|
414
790
|
var init_parser = __esm({
|
|
415
791
|
"node_modules/@xubylele/schema-forge-core/dist/core/parser.js"() {
|
|
416
792
|
"use strict";
|
|
793
|
+
init_normalize();
|
|
417
794
|
POLICY_COMMANDS = ["select", "insert", "update", "delete", "all"];
|
|
418
795
|
}
|
|
419
796
|
});
|
|
420
797
|
|
|
421
|
-
// node_modules/@xubylele/schema-forge-core/dist/core/normalize.js
|
|
422
|
-
function normalizeIdent(input) {
|
|
423
|
-
return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
|
|
424
|
-
}
|
|
425
|
-
function pkName(table) {
|
|
426
|
-
return `pk_${normalizeIdent(table)}`;
|
|
427
|
-
}
|
|
428
|
-
function uqName(table, column) {
|
|
429
|
-
return `uq_${normalizeIdent(table)}_${normalizeIdent(column)}`;
|
|
430
|
-
}
|
|
431
|
-
function legacyPkName(table) {
|
|
432
|
-
return `${normalizeIdent(table)}_pkey`;
|
|
433
|
-
}
|
|
434
|
-
function legacyUqName(table, column) {
|
|
435
|
-
return `${normalizeIdent(table)}_${normalizeIdent(column)}_key`;
|
|
436
|
-
}
|
|
437
|
-
function normalizeSpacesOutsideQuotes(value) {
|
|
438
|
-
let result = "";
|
|
439
|
-
let inSingleQuote = false;
|
|
440
|
-
let inDoubleQuote = false;
|
|
441
|
-
let pendingSpace = false;
|
|
442
|
-
for (const char of value) {
|
|
443
|
-
if (char === "'" && !inDoubleQuote) {
|
|
444
|
-
if (pendingSpace && result.length > 0 && result[result.length - 1] !== " ") {
|
|
445
|
-
result += " ";
|
|
446
|
-
}
|
|
447
|
-
pendingSpace = false;
|
|
448
|
-
inSingleQuote = !inSingleQuote;
|
|
449
|
-
result += char;
|
|
450
|
-
continue;
|
|
451
|
-
}
|
|
452
|
-
if (char === '"' && !inSingleQuote) {
|
|
453
|
-
if (pendingSpace && result.length > 0 && result[result.length - 1] !== " ") {
|
|
454
|
-
result += " ";
|
|
455
|
-
}
|
|
456
|
-
pendingSpace = false;
|
|
457
|
-
inDoubleQuote = !inDoubleQuote;
|
|
458
|
-
result += char;
|
|
459
|
-
continue;
|
|
460
|
-
}
|
|
461
|
-
if (!inSingleQuote && !inDoubleQuote && /\s/.test(char)) {
|
|
462
|
-
pendingSpace = true;
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
if (pendingSpace && result.length > 0 && result[result.length - 1] !== " ") {
|
|
466
|
-
result += " ";
|
|
467
|
-
}
|
|
468
|
-
pendingSpace = false;
|
|
469
|
-
result += char;
|
|
470
|
-
}
|
|
471
|
-
return result.trim();
|
|
472
|
-
}
|
|
473
|
-
function normalizeKnownFunctionsOutsideQuotes(value) {
|
|
474
|
-
let result = "";
|
|
475
|
-
let inSingleQuote = false;
|
|
476
|
-
let inDoubleQuote = false;
|
|
477
|
-
let buffer = "";
|
|
478
|
-
function flushBuffer() {
|
|
479
|
-
if (!buffer) {
|
|
480
|
-
return;
|
|
481
|
-
}
|
|
482
|
-
result += buffer.replace(/\bnow\s*\(\s*\)/gi, "now()").replace(/\bgen_random_uuid\s*\(\s*\)/gi, "gen_random_uuid()");
|
|
483
|
-
buffer = "";
|
|
484
|
-
}
|
|
485
|
-
for (const char of value) {
|
|
486
|
-
if (char === "'" && !inDoubleQuote) {
|
|
487
|
-
flushBuffer();
|
|
488
|
-
inSingleQuote = !inSingleQuote;
|
|
489
|
-
result += char;
|
|
490
|
-
continue;
|
|
491
|
-
}
|
|
492
|
-
if (char === '"' && !inSingleQuote) {
|
|
493
|
-
flushBuffer();
|
|
494
|
-
inDoubleQuote = !inDoubleQuote;
|
|
495
|
-
result += char;
|
|
496
|
-
continue;
|
|
497
|
-
}
|
|
498
|
-
if (inSingleQuote || inDoubleQuote) {
|
|
499
|
-
result += char;
|
|
500
|
-
continue;
|
|
501
|
-
}
|
|
502
|
-
buffer += char;
|
|
503
|
-
}
|
|
504
|
-
flushBuffer();
|
|
505
|
-
return result;
|
|
506
|
-
}
|
|
507
|
-
function normalizePunctuationOutsideQuotes(value) {
|
|
508
|
-
let result = "";
|
|
509
|
-
let inSingleQuote = false;
|
|
510
|
-
let inDoubleQuote = false;
|
|
511
|
-
for (let index = 0; index < value.length; index++) {
|
|
512
|
-
const char = value[index];
|
|
513
|
-
if (char === "'" && !inDoubleQuote) {
|
|
514
|
-
inSingleQuote = !inSingleQuote;
|
|
515
|
-
result += char;
|
|
516
|
-
continue;
|
|
517
|
-
}
|
|
518
|
-
if (char === '"' && !inSingleQuote) {
|
|
519
|
-
inDoubleQuote = !inDoubleQuote;
|
|
520
|
-
result += char;
|
|
521
|
-
continue;
|
|
522
|
-
}
|
|
523
|
-
if (!inSingleQuote && !inDoubleQuote && (char === "(" || char === ")")) {
|
|
524
|
-
while (result.endsWith(" ")) {
|
|
525
|
-
result = result.slice(0, -1);
|
|
526
|
-
}
|
|
527
|
-
result += char;
|
|
528
|
-
let lookahead = index + 1;
|
|
529
|
-
while (lookahead < value.length && value[lookahead] === " ") {
|
|
530
|
-
lookahead++;
|
|
531
|
-
}
|
|
532
|
-
index = lookahead - 1;
|
|
533
|
-
continue;
|
|
534
|
-
}
|
|
535
|
-
if (!inSingleQuote && !inDoubleQuote && char === ",") {
|
|
536
|
-
while (result.endsWith(" ")) {
|
|
537
|
-
result = result.slice(0, -1);
|
|
538
|
-
}
|
|
539
|
-
result += ", ";
|
|
540
|
-
let lookahead = index + 1;
|
|
541
|
-
while (lookahead < value.length && value[lookahead] === " ") {
|
|
542
|
-
lookahead++;
|
|
543
|
-
}
|
|
544
|
-
index = lookahead - 1;
|
|
545
|
-
continue;
|
|
546
|
-
}
|
|
547
|
-
result += char;
|
|
548
|
-
}
|
|
549
|
-
return result;
|
|
550
|
-
}
|
|
551
|
-
function normalizeDefault(expr) {
|
|
552
|
-
if (expr === void 0 || expr === null) {
|
|
553
|
-
return null;
|
|
554
|
-
}
|
|
555
|
-
const trimmed = expr.trim();
|
|
556
|
-
if (trimmed.length === 0) {
|
|
557
|
-
return null;
|
|
558
|
-
}
|
|
559
|
-
const normalizedSpacing = normalizeSpacesOutsideQuotes(trimmed);
|
|
560
|
-
const normalizedPunctuation = normalizePunctuationOutsideQuotes(normalizedSpacing);
|
|
561
|
-
return normalizeKnownFunctionsOutsideQuotes(normalizedPunctuation);
|
|
562
|
-
}
|
|
563
|
-
var init_normalize = __esm({
|
|
564
|
-
"node_modules/@xubylele/schema-forge-core/dist/core/normalize.js"() {
|
|
565
|
-
"use strict";
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
|
|
569
798
|
// node_modules/@xubylele/schema-forge-core/dist/core/diff.js
|
|
570
799
|
function getTableNamesFromState(state) {
|
|
571
800
|
return new Set(Object.keys(state.tables));
|
|
@@ -610,6 +839,62 @@ function policyEquals(oldP, newP) {
|
|
|
610
839
|
return false;
|
|
611
840
|
return true;
|
|
612
841
|
}
|
|
842
|
+
function resolveSchemaIndexName(index) {
|
|
843
|
+
if (index.name && index.name.trim().length > 0) {
|
|
844
|
+
return index.name.trim();
|
|
845
|
+
}
|
|
846
|
+
return deterministicIndexName({
|
|
847
|
+
table: index.table,
|
|
848
|
+
columns: index.columns,
|
|
849
|
+
expression: index.expression
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
function resolveStateIndexName(index) {
|
|
853
|
+
if (index.name && index.name.trim().length > 0) {
|
|
854
|
+
return index.name.trim();
|
|
855
|
+
}
|
|
856
|
+
return deterministicIndexName({
|
|
857
|
+
table: index.table,
|
|
858
|
+
columns: index.columns,
|
|
859
|
+
expression: index.expression
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
function normalizeIndexWhere(value) {
|
|
863
|
+
return normalizeSqlExpression(value);
|
|
864
|
+
}
|
|
865
|
+
function normalizeIndexExpression(value) {
|
|
866
|
+
return normalizeSqlExpression(value);
|
|
867
|
+
}
|
|
868
|
+
function indexesEqual(previous, current) {
|
|
869
|
+
if (previous.table !== current.table)
|
|
870
|
+
return false;
|
|
871
|
+
if ((previous.unique ?? false) !== (current.unique ?? false))
|
|
872
|
+
return false;
|
|
873
|
+
if (previous.columns.length !== current.columns.length)
|
|
874
|
+
return false;
|
|
875
|
+
for (let index = 0; index < previous.columns.length; index++) {
|
|
876
|
+
if (previous.columns[index] !== current.columns[index]) {
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
if (normalizeIndexWhere(previous.where) !== normalizeIndexWhere(current.where))
|
|
881
|
+
return false;
|
|
882
|
+
if (normalizeIndexExpression(previous.expression) !== normalizeIndexExpression(current.expression))
|
|
883
|
+
return false;
|
|
884
|
+
return true;
|
|
885
|
+
}
|
|
886
|
+
function resolveStateViewHash(view) {
|
|
887
|
+
if (view.hash && view.hash.trim().length > 0) {
|
|
888
|
+
return view.hash;
|
|
889
|
+
}
|
|
890
|
+
return hashSqlContent(view.query);
|
|
891
|
+
}
|
|
892
|
+
function resolveSchemaViewHash(view) {
|
|
893
|
+
if (view.hash && view.hash.trim().length > 0) {
|
|
894
|
+
return view.hash;
|
|
895
|
+
}
|
|
896
|
+
return hashSqlContent(view.query);
|
|
897
|
+
}
|
|
613
898
|
function diffSchemas(oldState, newSchema) {
|
|
614
899
|
const operations = [];
|
|
615
900
|
const oldTableNames = getTableNamesFromState(oldState);
|
|
@@ -622,6 +907,15 @@ function diffSchemas(oldState, newSchema) {
|
|
|
622
907
|
kind: "create_table",
|
|
623
908
|
table: newSchema.tables[tableName]
|
|
624
909
|
});
|
|
910
|
+
const createdTable = newSchema.tables[tableName];
|
|
911
|
+
const createdIndexes = (createdTable.indexes ?? []).map((index) => ({ ...index, name: resolveSchemaIndexName(index) })).sort((left, right) => left.name.localeCompare(right.name));
|
|
912
|
+
for (const index of createdIndexes) {
|
|
913
|
+
operations.push({
|
|
914
|
+
kind: "create_index",
|
|
915
|
+
tableName,
|
|
916
|
+
index
|
|
917
|
+
});
|
|
918
|
+
}
|
|
625
919
|
}
|
|
626
920
|
}
|
|
627
921
|
const commonTableNames = sortedNewTableNames.filter((tableName) => oldTableNames.has(tableName));
|
|
@@ -752,20 +1046,79 @@ function diffSchemas(oldState, newSchema) {
|
|
|
752
1046
|
});
|
|
753
1047
|
}
|
|
754
1048
|
}
|
|
755
|
-
}
|
|
756
|
-
for (const tableName of commonTableNames) {
|
|
757
|
-
const newTable = newSchema.tables[tableName];
|
|
758
|
-
const oldTable = oldState.tables[tableName];
|
|
759
|
-
if (!newTable || !oldTable) {
|
|
760
|
-
continue;
|
|
1049
|
+
}
|
|
1050
|
+
for (const tableName of commonTableNames) {
|
|
1051
|
+
const newTable = newSchema.tables[tableName];
|
|
1052
|
+
const oldTable = oldState.tables[tableName];
|
|
1053
|
+
if (!newTable || !oldTable) {
|
|
1054
|
+
continue;
|
|
1055
|
+
}
|
|
1056
|
+
const previousPrimaryKey = resolveStatePrimaryKey(oldTable);
|
|
1057
|
+
const currentPrimaryKey = resolveSchemaPrimaryKey(newTable);
|
|
1058
|
+
if (currentPrimaryKey !== null && previousPrimaryKey !== currentPrimaryKey) {
|
|
1059
|
+
operations.push({
|
|
1060
|
+
kind: "add_primary_key_constraint",
|
|
1061
|
+
tableName,
|
|
1062
|
+
columnName: currentPrimaryKey
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
for (const tableName of commonTableNames) {
|
|
1067
|
+
const newTable = newSchema.tables[tableName];
|
|
1068
|
+
const oldTable = oldState.tables[tableName];
|
|
1069
|
+
if (!newTable || !oldTable) {
|
|
1070
|
+
continue;
|
|
1071
|
+
}
|
|
1072
|
+
const oldIndexes = oldTable.indexes ?? {};
|
|
1073
|
+
const newIndexesByName = /* @__PURE__ */ new Map();
|
|
1074
|
+
for (const index of newTable.indexes ?? []) {
|
|
1075
|
+
const resolved = { ...index, name: resolveSchemaIndexName(index) };
|
|
1076
|
+
newIndexesByName.set(resolved.name, resolved);
|
|
1077
|
+
}
|
|
1078
|
+
const oldIndexesByName = /* @__PURE__ */ new Map();
|
|
1079
|
+
for (const oldIndex of Object.values(oldIndexes)) {
|
|
1080
|
+
oldIndexesByName.set(resolveStateIndexName(oldIndex), oldIndex);
|
|
1081
|
+
}
|
|
1082
|
+
const oldIndexNames = Array.from(oldIndexesByName.keys()).sort((a, b) => a.localeCompare(b));
|
|
1083
|
+
const newIndexNames = Array.from(newIndexesByName.keys()).sort((a, b) => a.localeCompare(b));
|
|
1084
|
+
const indexDrops = [];
|
|
1085
|
+
const indexCreates = [];
|
|
1086
|
+
for (const oldIndexName of oldIndexNames) {
|
|
1087
|
+
const oldIndex = oldIndexesByName.get(oldIndexName);
|
|
1088
|
+
if (!oldIndex) {
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
const nextIndex = newIndexesByName.get(oldIndexName);
|
|
1092
|
+
if (!nextIndex) {
|
|
1093
|
+
indexDrops.push({ ...oldIndex, name: oldIndexName });
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
if (!indexesEqual(oldIndex, nextIndex)) {
|
|
1097
|
+
indexDrops.push({ ...oldIndex, name: oldIndexName });
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
for (const newIndexName of newIndexNames) {
|
|
1101
|
+
const newIndex = newIndexesByName.get(newIndexName);
|
|
1102
|
+
if (!newIndex) {
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
const oldIndex = oldIndexesByName.get(newIndexName);
|
|
1106
|
+
if (!oldIndex || !indexesEqual(oldIndex, newIndex)) {
|
|
1107
|
+
indexCreates.push(newIndex);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
for (const index of indexDrops) {
|
|
1111
|
+
operations.push({
|
|
1112
|
+
kind: "drop_index",
|
|
1113
|
+
tableName,
|
|
1114
|
+
index
|
|
1115
|
+
});
|
|
761
1116
|
}
|
|
762
|
-
const
|
|
763
|
-
const currentPrimaryKey = resolveSchemaPrimaryKey(newTable);
|
|
764
|
-
if (currentPrimaryKey !== null && previousPrimaryKey !== currentPrimaryKey) {
|
|
1117
|
+
for (const index of indexCreates) {
|
|
765
1118
|
operations.push({
|
|
766
|
-
kind: "
|
|
1119
|
+
kind: "create_index",
|
|
767
1120
|
tableName,
|
|
768
|
-
|
|
1121
|
+
index
|
|
769
1122
|
});
|
|
770
1123
|
}
|
|
771
1124
|
}
|
|
@@ -817,6 +1170,36 @@ function diffSchemas(oldState, newSchema) {
|
|
|
817
1170
|
}
|
|
818
1171
|
}
|
|
819
1172
|
}
|
|
1173
|
+
const oldViews = oldState.views ?? {};
|
|
1174
|
+
const newViews = newSchema.views ?? {};
|
|
1175
|
+
const oldViewNames = Object.keys(oldViews).sort((a, b) => a.localeCompare(b));
|
|
1176
|
+
const newViewNames = Object.keys(newViews).sort((a, b) => a.localeCompare(b));
|
|
1177
|
+
const oldViewNameSet = new Set(oldViewNames);
|
|
1178
|
+
const newViewNameSet = new Set(newViewNames);
|
|
1179
|
+
for (const viewName of newViewNames) {
|
|
1180
|
+
const nextView = newViews[viewName];
|
|
1181
|
+
if (!nextView) {
|
|
1182
|
+
continue;
|
|
1183
|
+
}
|
|
1184
|
+
if (!oldViewNameSet.has(viewName)) {
|
|
1185
|
+
operations.push({ kind: "create_view", view: nextView });
|
|
1186
|
+
continue;
|
|
1187
|
+
}
|
|
1188
|
+
const prevView = oldViews[viewName];
|
|
1189
|
+
if (!prevView) {
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
const oldHash = resolveStateViewHash(prevView);
|
|
1193
|
+
const newHash = resolveSchemaViewHash(nextView);
|
|
1194
|
+
if (oldHash !== newHash) {
|
|
1195
|
+
operations.push({ kind: "replace_view", view: nextView });
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
for (const viewName of oldViewNames) {
|
|
1199
|
+
if (!newViewNameSet.has(viewName)) {
|
|
1200
|
+
operations.push({ kind: "drop_view", viewName });
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
820
1203
|
for (const tableName of sortedOldTableNames) {
|
|
821
1204
|
if (!newTableNames.has(tableName)) {
|
|
822
1205
|
operations.push({
|
|
@@ -916,9 +1299,52 @@ function validateSchema(schema) {
|
|
|
916
1299
|
for (const tableName in schema.tables) {
|
|
917
1300
|
const table = schema.tables[tableName];
|
|
918
1301
|
validateTableColumns(tableName, table, schema.tables);
|
|
1302
|
+
validateTableIndexes(tableName, table, schema.tables);
|
|
919
1303
|
}
|
|
920
1304
|
validatePolicies(schema);
|
|
921
1305
|
}
|
|
1306
|
+
function validateTableIndexes(tableName, table, allTables) {
|
|
1307
|
+
if (!table.indexes?.length) {
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
const normalizedNames = /* @__PURE__ */ new Set();
|
|
1311
|
+
for (const index of table.indexes) {
|
|
1312
|
+
validateSingleIndex(tableName, table, index, allTables);
|
|
1313
|
+
const effectiveName = index.name?.trim() || deterministicIndexName({
|
|
1314
|
+
table: index.table,
|
|
1315
|
+
columns: index.columns,
|
|
1316
|
+
expression: index.expression
|
|
1317
|
+
});
|
|
1318
|
+
if (normalizedNames.has(effectiveName)) {
|
|
1319
|
+
throw new Error(`Table '${tableName}': duplicate index name '${effectiveName}'`);
|
|
1320
|
+
}
|
|
1321
|
+
normalizedNames.add(effectiveName);
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
function validateSingleIndex(tableName, table, index, allTables) {
|
|
1325
|
+
if (!allTables[index.table]) {
|
|
1326
|
+
throw new Error(`Table '${tableName}': index '${index.name}' references table '${index.table}' which does not exist`);
|
|
1327
|
+
}
|
|
1328
|
+
if (index.table !== tableName) {
|
|
1329
|
+
throw new Error(`Table '${tableName}': index '${index.name}' must target table '${tableName}'`);
|
|
1330
|
+
}
|
|
1331
|
+
const hasColumns = index.columns.length > 0;
|
|
1332
|
+
const hasExpression = index.expression !== void 0;
|
|
1333
|
+
if (hasColumns && hasExpression || !hasColumns && !hasExpression) {
|
|
1334
|
+
throw new Error(`Table '${tableName}': index '${index.name}' must define exactly one of columns or expression`);
|
|
1335
|
+
}
|
|
1336
|
+
if (hasExpression && index.expression.trim().length === 0) {
|
|
1337
|
+
throw new Error(`Table '${tableName}': index '${index.name}' expression cannot be empty`);
|
|
1338
|
+
}
|
|
1339
|
+
if (hasColumns) {
|
|
1340
|
+
const tableColumns = new Set(table.columns.map((column) => column.name));
|
|
1341
|
+
for (const columnName of index.columns) {
|
|
1342
|
+
if (!tableColumns.has(columnName)) {
|
|
1343
|
+
throw new Error(`Table '${tableName}': index '${index.name}' references unknown column '${columnName}'`);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
922
1348
|
function validateDuplicateTables(schema) {
|
|
923
1349
|
const tableNames = Object.keys(schema.tables);
|
|
924
1350
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -997,6 +1423,7 @@ var VALID_POLICY_COMMANDS, VALID_BASE_COLUMN_TYPES;
|
|
|
997
1423
|
var init_validator = __esm({
|
|
998
1424
|
"node_modules/@xubylele/schema-forge-core/dist/core/validator.js"() {
|
|
999
1425
|
"use strict";
|
|
1426
|
+
init_normalize();
|
|
1000
1427
|
VALID_POLICY_COMMANDS = [
|
|
1001
1428
|
"select",
|
|
1002
1429
|
"insert",
|
|
@@ -1097,12 +1524,22 @@ function classifyOperation(operation) {
|
|
|
1097
1524
|
return "DESTRUCTIVE";
|
|
1098
1525
|
case "add_primary_key_constraint":
|
|
1099
1526
|
return "SAFE";
|
|
1527
|
+
case "create_index":
|
|
1528
|
+
return "SAFE";
|
|
1529
|
+
case "drop_index":
|
|
1530
|
+
return "WARNING";
|
|
1100
1531
|
case "create_policy":
|
|
1101
1532
|
return "SAFE";
|
|
1102
1533
|
case "drop_policy":
|
|
1103
1534
|
return "DESTRUCTIVE";
|
|
1104
1535
|
case "modify_policy":
|
|
1105
1536
|
return "WARNING";
|
|
1537
|
+
case "create_view":
|
|
1538
|
+
return "SAFE";
|
|
1539
|
+
case "drop_view":
|
|
1540
|
+
return "DESTRUCTIVE";
|
|
1541
|
+
case "replace_view":
|
|
1542
|
+
return "WARNING";
|
|
1106
1543
|
default:
|
|
1107
1544
|
const _exhaustive = operation;
|
|
1108
1545
|
return _exhaustive;
|
|
@@ -1225,6 +1662,14 @@ function checkOperationSafety(operation) {
|
|
|
1225
1662
|
message: "Primary key constraint removed",
|
|
1226
1663
|
operationKind: operation.kind
|
|
1227
1664
|
};
|
|
1665
|
+
case "drop_index":
|
|
1666
|
+
return {
|
|
1667
|
+
safetyLevel,
|
|
1668
|
+
code: "DROP_INDEX",
|
|
1669
|
+
table: operation.tableName,
|
|
1670
|
+
message: `Index '${operation.index.name}' removed`,
|
|
1671
|
+
operationKind: operation.kind
|
|
1672
|
+
};
|
|
1228
1673
|
case "drop_policy":
|
|
1229
1674
|
return {
|
|
1230
1675
|
safetyLevel,
|
|
@@ -1241,6 +1686,22 @@ function checkOperationSafety(operation) {
|
|
|
1241
1686
|
message: "Policy expression changed",
|
|
1242
1687
|
operationKind: operation.kind
|
|
1243
1688
|
};
|
|
1689
|
+
case "drop_view":
|
|
1690
|
+
return {
|
|
1691
|
+
safetyLevel,
|
|
1692
|
+
code: "DROP_VIEW",
|
|
1693
|
+
table: operation.viewName,
|
|
1694
|
+
message: "View removed",
|
|
1695
|
+
operationKind: operation.kind
|
|
1696
|
+
};
|
|
1697
|
+
case "replace_view":
|
|
1698
|
+
return {
|
|
1699
|
+
safetyLevel,
|
|
1700
|
+
code: "REPLACE_VIEW",
|
|
1701
|
+
table: operation.view.name,
|
|
1702
|
+
message: "View definition changed",
|
|
1703
|
+
operationKind: operation.kind
|
|
1704
|
+
};
|
|
1244
1705
|
default:
|
|
1245
1706
|
return null;
|
|
1246
1707
|
}
|
|
@@ -1402,6 +1863,7 @@ var init_fs = __esm({
|
|
|
1402
1863
|
// node_modules/@xubylele/schema-forge-core/dist/core/state-transform.js
|
|
1403
1864
|
async function schemaToState(schema) {
|
|
1404
1865
|
const tables = {};
|
|
1866
|
+
let views;
|
|
1405
1867
|
for (const [tableName, table] of Object.entries(schema.tables)) {
|
|
1406
1868
|
const columns = {};
|
|
1407
1869
|
const primaryKeyColumn = table.primaryKey ?? table.columns.find((column) => column.primaryKey)?.name ?? null;
|
|
@@ -1426,15 +1888,40 @@ async function schemaToState(schema) {
|
|
|
1426
1888
|
};
|
|
1427
1889
|
}
|
|
1428
1890
|
}
|
|
1891
|
+
let indexes;
|
|
1892
|
+
if (table.indexes?.length) {
|
|
1893
|
+
indexes = {};
|
|
1894
|
+
for (const index of table.indexes) {
|
|
1895
|
+
indexes[index.name] = {
|
|
1896
|
+
name: index.name,
|
|
1897
|
+
table: index.table,
|
|
1898
|
+
columns: [...index.columns],
|
|
1899
|
+
unique: index.unique,
|
|
1900
|
+
...index.where !== void 0 && { where: index.where },
|
|
1901
|
+
...index.expression !== void 0 && { expression: index.expression }
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1429
1905
|
tables[tableName] = {
|
|
1430
1906
|
columns,
|
|
1431
1907
|
...primaryKeyColumn !== null && { primaryKey: primaryKeyColumn },
|
|
1908
|
+
...indexes !== void 0 && { indexes },
|
|
1432
1909
|
...policies !== void 0 && { policies }
|
|
1433
1910
|
};
|
|
1434
1911
|
}
|
|
1912
|
+
if (schema.views && Object.keys(schema.views).length > 0) {
|
|
1913
|
+
views = {};
|
|
1914
|
+
for (const [viewName, view] of Object.entries(schema.views)) {
|
|
1915
|
+
views[viewName] = {
|
|
1916
|
+
query: view.query,
|
|
1917
|
+
hash: view.hash
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1435
1921
|
return {
|
|
1436
1922
|
version: 1,
|
|
1437
|
-
tables
|
|
1923
|
+
tables,
|
|
1924
|
+
...views !== void 0 && { views }
|
|
1438
1925
|
};
|
|
1439
1926
|
}
|
|
1440
1927
|
var init_state_transform = __esm({
|
|
@@ -1463,6 +1950,242 @@ var init_state_manager = __esm({
|
|
|
1463
1950
|
}
|
|
1464
1951
|
});
|
|
1465
1952
|
|
|
1953
|
+
// node_modules/@xubylele/schema-forge-core/dist/core/plan-builder.js
|
|
1954
|
+
function toAction(operation) {
|
|
1955
|
+
switch (operation.kind) {
|
|
1956
|
+
case "create_table":
|
|
1957
|
+
case "add_column":
|
|
1958
|
+
case "add_primary_key_constraint":
|
|
1959
|
+
case "create_index":
|
|
1960
|
+
case "create_policy":
|
|
1961
|
+
case "create_view":
|
|
1962
|
+
return "create";
|
|
1963
|
+
case "drop_table":
|
|
1964
|
+
case "drop_column":
|
|
1965
|
+
case "drop_primary_key_constraint":
|
|
1966
|
+
case "drop_index":
|
|
1967
|
+
case "drop_policy":
|
|
1968
|
+
case "drop_view":
|
|
1969
|
+
return "delete";
|
|
1970
|
+
case "column_type_changed":
|
|
1971
|
+
case "column_nullability_changed":
|
|
1972
|
+
case "column_default_changed":
|
|
1973
|
+
case "column_unique_changed":
|
|
1974
|
+
case "modify_policy":
|
|
1975
|
+
case "replace_view":
|
|
1976
|
+
return "modify";
|
|
1977
|
+
default: {
|
|
1978
|
+
const exhaustiveCheck = operation;
|
|
1979
|
+
return exhaustiveCheck;
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
function actionToSymbol(action) {
|
|
1984
|
+
if (action === "create") {
|
|
1985
|
+
return "+";
|
|
1986
|
+
}
|
|
1987
|
+
if (action === "modify") {
|
|
1988
|
+
return "~";
|
|
1989
|
+
}
|
|
1990
|
+
return "-";
|
|
1991
|
+
}
|
|
1992
|
+
function formatNullable(value) {
|
|
1993
|
+
return value ? "nullable" : "not null";
|
|
1994
|
+
}
|
|
1995
|
+
function formatUnique(value) {
|
|
1996
|
+
return value ? "unique" : "not unique";
|
|
1997
|
+
}
|
|
1998
|
+
function formatDefault(value) {
|
|
1999
|
+
if (value === null) {
|
|
2000
|
+
return "null";
|
|
2001
|
+
}
|
|
2002
|
+
return value;
|
|
2003
|
+
}
|
|
2004
|
+
function toEntry(operation) {
|
|
2005
|
+
const action = toAction(operation);
|
|
2006
|
+
const symbol = actionToSymbol(action);
|
|
2007
|
+
switch (operation.kind) {
|
|
2008
|
+
case "create_table":
|
|
2009
|
+
return {
|
|
2010
|
+
action,
|
|
2011
|
+
symbol,
|
|
2012
|
+
operationKind: operation.kind,
|
|
2013
|
+
summary: `create table ${operation.table.name}`,
|
|
2014
|
+
tableName: operation.table.name
|
|
2015
|
+
};
|
|
2016
|
+
case "drop_table":
|
|
2017
|
+
return {
|
|
2018
|
+
action,
|
|
2019
|
+
symbol,
|
|
2020
|
+
operationKind: operation.kind,
|
|
2021
|
+
summary: `drop table ${operation.tableName}`,
|
|
2022
|
+
tableName: operation.tableName
|
|
2023
|
+
};
|
|
2024
|
+
case "add_column":
|
|
2025
|
+
return {
|
|
2026
|
+
action,
|
|
2027
|
+
symbol,
|
|
2028
|
+
operationKind: operation.kind,
|
|
2029
|
+
summary: `add column ${operation.column.name} to ${operation.tableName}`,
|
|
2030
|
+
tableName: operation.tableName,
|
|
2031
|
+
columnName: operation.column.name
|
|
2032
|
+
};
|
|
2033
|
+
case "drop_column":
|
|
2034
|
+
return {
|
|
2035
|
+
action,
|
|
2036
|
+
symbol,
|
|
2037
|
+
operationKind: operation.kind,
|
|
2038
|
+
summary: `drop column ${operation.columnName} from ${operation.tableName}`,
|
|
2039
|
+
tableName: operation.tableName,
|
|
2040
|
+
columnName: operation.columnName
|
|
2041
|
+
};
|
|
2042
|
+
case "column_type_changed":
|
|
2043
|
+
return {
|
|
2044
|
+
action,
|
|
2045
|
+
symbol,
|
|
2046
|
+
operationKind: operation.kind,
|
|
2047
|
+
summary: `modify column ${operation.columnName} type on ${operation.tableName} (${operation.fromType} -> ${operation.toType})`,
|
|
2048
|
+
tableName: operation.tableName,
|
|
2049
|
+
columnName: operation.columnName,
|
|
2050
|
+
from: operation.fromType,
|
|
2051
|
+
to: operation.toType
|
|
2052
|
+
};
|
|
2053
|
+
case "column_nullability_changed":
|
|
2054
|
+
return {
|
|
2055
|
+
action,
|
|
2056
|
+
symbol,
|
|
2057
|
+
operationKind: operation.kind,
|
|
2058
|
+
summary: `modify column ${operation.columnName} nullability on ${operation.tableName} (${formatNullable(operation.from)} -> ${formatNullable(operation.to)})`,
|
|
2059
|
+
tableName: operation.tableName,
|
|
2060
|
+
columnName: operation.columnName,
|
|
2061
|
+
from: operation.from,
|
|
2062
|
+
to: operation.to
|
|
2063
|
+
};
|
|
2064
|
+
case "column_default_changed":
|
|
2065
|
+
return {
|
|
2066
|
+
action,
|
|
2067
|
+
symbol,
|
|
2068
|
+
operationKind: operation.kind,
|
|
2069
|
+
summary: `modify column ${operation.columnName} default on ${operation.tableName} (${formatDefault(operation.fromDefault)} -> ${formatDefault(operation.toDefault)})`,
|
|
2070
|
+
tableName: operation.tableName,
|
|
2071
|
+
columnName: operation.columnName,
|
|
2072
|
+
from: operation.fromDefault,
|
|
2073
|
+
to: operation.toDefault
|
|
2074
|
+
};
|
|
2075
|
+
case "column_unique_changed":
|
|
2076
|
+
return {
|
|
2077
|
+
action,
|
|
2078
|
+
symbol,
|
|
2079
|
+
operationKind: operation.kind,
|
|
2080
|
+
summary: `modify column ${operation.columnName} unique constraint on ${operation.tableName} (${formatUnique(operation.from)} -> ${formatUnique(operation.to)})`,
|
|
2081
|
+
tableName: operation.tableName,
|
|
2082
|
+
columnName: operation.columnName,
|
|
2083
|
+
from: operation.from,
|
|
2084
|
+
to: operation.to
|
|
2085
|
+
};
|
|
2086
|
+
case "drop_primary_key_constraint":
|
|
2087
|
+
return {
|
|
2088
|
+
action,
|
|
2089
|
+
symbol,
|
|
2090
|
+
operationKind: operation.kind,
|
|
2091
|
+
summary: `drop primary key constraint on ${operation.tableName}`,
|
|
2092
|
+
tableName: operation.tableName
|
|
2093
|
+
};
|
|
2094
|
+
case "add_primary_key_constraint":
|
|
2095
|
+
return {
|
|
2096
|
+
action,
|
|
2097
|
+
symbol,
|
|
2098
|
+
operationKind: operation.kind,
|
|
2099
|
+
summary: `add primary key constraint on ${operation.tableName} (${operation.columnName})`,
|
|
2100
|
+
tableName: operation.tableName,
|
|
2101
|
+
columnName: operation.columnName
|
|
2102
|
+
};
|
|
2103
|
+
case "create_index":
|
|
2104
|
+
return {
|
|
2105
|
+
action,
|
|
2106
|
+
symbol,
|
|
2107
|
+
operationKind: operation.kind,
|
|
2108
|
+
summary: `create index ${operation.index.name} on ${operation.tableName}`,
|
|
2109
|
+
tableName: operation.tableName
|
|
2110
|
+
};
|
|
2111
|
+
case "drop_index":
|
|
2112
|
+
return {
|
|
2113
|
+
action,
|
|
2114
|
+
symbol,
|
|
2115
|
+
operationKind: operation.kind,
|
|
2116
|
+
summary: `drop index ${operation.index.name} on ${operation.tableName}`,
|
|
2117
|
+
tableName: operation.tableName
|
|
2118
|
+
};
|
|
2119
|
+
case "create_policy":
|
|
2120
|
+
return {
|
|
2121
|
+
action,
|
|
2122
|
+
symbol,
|
|
2123
|
+
operationKind: operation.kind,
|
|
2124
|
+
summary: `create policy ${operation.policy.name} on ${operation.tableName}`,
|
|
2125
|
+
tableName: operation.tableName
|
|
2126
|
+
};
|
|
2127
|
+
case "drop_policy":
|
|
2128
|
+
return {
|
|
2129
|
+
action,
|
|
2130
|
+
symbol,
|
|
2131
|
+
operationKind: operation.kind,
|
|
2132
|
+
summary: `drop policy ${operation.policyName} on ${operation.tableName}`,
|
|
2133
|
+
tableName: operation.tableName
|
|
2134
|
+
};
|
|
2135
|
+
case "modify_policy":
|
|
2136
|
+
return {
|
|
2137
|
+
action,
|
|
2138
|
+
symbol,
|
|
2139
|
+
operationKind: operation.kind,
|
|
2140
|
+
summary: `modify policy ${operation.policyName} on ${operation.tableName}`,
|
|
2141
|
+
tableName: operation.tableName
|
|
2142
|
+
};
|
|
2143
|
+
case "create_view":
|
|
2144
|
+
return {
|
|
2145
|
+
action,
|
|
2146
|
+
symbol,
|
|
2147
|
+
operationKind: operation.kind,
|
|
2148
|
+
summary: `create view ${operation.view.name}`
|
|
2149
|
+
};
|
|
2150
|
+
case "drop_view":
|
|
2151
|
+
return {
|
|
2152
|
+
action,
|
|
2153
|
+
symbol,
|
|
2154
|
+
operationKind: operation.kind,
|
|
2155
|
+
summary: `drop view ${operation.viewName}`
|
|
2156
|
+
};
|
|
2157
|
+
case "replace_view":
|
|
2158
|
+
return {
|
|
2159
|
+
action,
|
|
2160
|
+
symbol,
|
|
2161
|
+
operationKind: operation.kind,
|
|
2162
|
+
summary: `replace view ${operation.view.name}`
|
|
2163
|
+
};
|
|
2164
|
+
default: {
|
|
2165
|
+
const exhaustiveCheck = operation;
|
|
2166
|
+
return exhaustiveCheck;
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
function formatMigrationPlanLine(entry) {
|
|
2171
|
+
return `${entry.symbol} ${entry.summary}`;
|
|
2172
|
+
}
|
|
2173
|
+
function formatMigrationPlanLines(entries) {
|
|
2174
|
+
return entries.map(formatMigrationPlanLine);
|
|
2175
|
+
}
|
|
2176
|
+
function buildMigrationPlan(diff2) {
|
|
2177
|
+
const entries = diff2.operations.map(toEntry);
|
|
2178
|
+
return {
|
|
2179
|
+
entries,
|
|
2180
|
+
lines: formatMigrationPlanLines(entries)
|
|
2181
|
+
};
|
|
2182
|
+
}
|
|
2183
|
+
var init_plan_builder = __esm({
|
|
2184
|
+
"node_modules/@xubylele/schema-forge-core/dist/core/plan-builder.js"() {
|
|
2185
|
+
"use strict";
|
|
2186
|
+
}
|
|
2187
|
+
});
|
|
2188
|
+
|
|
1466
2189
|
// node_modules/@xubylele/schema-forge-core/dist/generator/sql-generator.js
|
|
1467
2190
|
function generateEnableRls(tableName) {
|
|
1468
2191
|
return `ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`;
|
|
@@ -1507,14 +2230,46 @@ function generateOperation(operation, provider, sqlConfig) {
|
|
|
1507
2230
|
return generateDropPrimaryKeyConstraint(operation.tableName);
|
|
1508
2231
|
case "add_primary_key_constraint":
|
|
1509
2232
|
return generateAddPrimaryKeyConstraint(operation.tableName, operation.columnName);
|
|
2233
|
+
case "create_index":
|
|
2234
|
+
return generateCreateIndex(operation.tableName, operation.index);
|
|
2235
|
+
case "drop_index":
|
|
2236
|
+
return generateDropIndex(operation.tableName, operation.index);
|
|
1510
2237
|
case "create_policy":
|
|
1511
2238
|
return generateCreatePolicy(operation.tableName, operation.policy);
|
|
1512
2239
|
case "drop_policy":
|
|
1513
2240
|
return generateDropPolicy(operation.tableName, operation.policyName);
|
|
2241
|
+
case "create_view":
|
|
2242
|
+
return generateCreateOrReplaceView(operation.view);
|
|
2243
|
+
case "drop_view":
|
|
2244
|
+
return generateDropView(operation.viewName);
|
|
2245
|
+
case "replace_view":
|
|
2246
|
+
return generateCreateOrReplaceView(operation.view);
|
|
1514
2247
|
case "modify_policy":
|
|
1515
2248
|
return generateModifyPolicy(operation.tableName, operation.policyName, operation.policy);
|
|
1516
2249
|
}
|
|
1517
2250
|
}
|
|
2251
|
+
function generateCreateOrReplaceView(view) {
|
|
2252
|
+
return `CREATE OR REPLACE VIEW ${view.name} AS
|
|
2253
|
+
${view.query};`;
|
|
2254
|
+
}
|
|
2255
|
+
function generateDropView(viewName) {
|
|
2256
|
+
return `DROP VIEW IF EXISTS ${viewName};`;
|
|
2257
|
+
}
|
|
2258
|
+
function generateCreateIndex(tableName, index) {
|
|
2259
|
+
const uniqueToken = index.unique ? "UNIQUE " : "";
|
|
2260
|
+
const payload = index.expression ? `(${index.expression})` : `(${index.columns.join(", ")})`;
|
|
2261
|
+
const whereClause = index.where ? ` WHERE ${index.where}` : "";
|
|
2262
|
+
return `CREATE ${uniqueToken}INDEX ${index.name} ON ${tableName} ${payload}${whereClause};`;
|
|
2263
|
+
}
|
|
2264
|
+
function generateDropIndex(tableName, index) {
|
|
2265
|
+
const fallbackName = deterministicIndexName({
|
|
2266
|
+
table: tableName,
|
|
2267
|
+
columns: index.columns,
|
|
2268
|
+
expression: index.expression
|
|
2269
|
+
});
|
|
2270
|
+
const indexNames = Array.from(/* @__PURE__ */ new Set([index.name, fallbackName]));
|
|
2271
|
+
return indexNames.map((indexName) => `DROP INDEX IF EXISTS ${indexName};`).join("\n");
|
|
2272
|
+
}
|
|
1518
2273
|
function generateCreateTable(table, provider, sqlConfig) {
|
|
1519
2274
|
const columnDefs = table.columns.map((col) => generateColumnDefinition(col, provider, sqlConfig));
|
|
1520
2275
|
const lines = ["CREATE TABLE " + table.name + " ("];
|
|
@@ -2558,6 +3313,26 @@ function renderPolicy(policy) {
|
|
|
2558
3313
|
}
|
|
2559
3314
|
return lines.join("\n");
|
|
2560
3315
|
}
|
|
3316
|
+
function renderIndex(index) {
|
|
3317
|
+
const resolvedName = index.name || deterministicIndexName({
|
|
3318
|
+
table: index.table,
|
|
3319
|
+
columns: index.columns,
|
|
3320
|
+
expression: index.expression
|
|
3321
|
+
});
|
|
3322
|
+
const lines = [`index ${resolvedName} on ${index.table}`];
|
|
3323
|
+
if (index.expression) {
|
|
3324
|
+
lines.push(`expression ${index.expression}`);
|
|
3325
|
+
} else {
|
|
3326
|
+
lines.push(`columns ${index.columns.join(", ")}`);
|
|
3327
|
+
}
|
|
3328
|
+
if (index.unique) {
|
|
3329
|
+
lines.push("unique");
|
|
3330
|
+
}
|
|
3331
|
+
if (index.where) {
|
|
3332
|
+
lines.push(`where ${index.where}`);
|
|
3333
|
+
}
|
|
3334
|
+
return lines.join("\n");
|
|
3335
|
+
}
|
|
2561
3336
|
function schemaToDsl(schema) {
|
|
2562
3337
|
const tableNames = Object.keys(schema.tables).sort((left, right) => left.localeCompare(right));
|
|
2563
3338
|
const blocks = [];
|
|
@@ -2569,6 +3344,17 @@ function schemaToDsl(schema) {
|
|
|
2569
3344
|
}
|
|
2570
3345
|
tableLines.push("}");
|
|
2571
3346
|
blocks.push(tableLines.join("\n"));
|
|
3347
|
+
const sortedIndexes = (table.indexes ?? []).map((index) => ({
|
|
3348
|
+
...index,
|
|
3349
|
+
name: index.name || deterministicIndexName({
|
|
3350
|
+
table: index.table,
|
|
3351
|
+
columns: index.columns,
|
|
3352
|
+
expression: index.expression
|
|
3353
|
+
})
|
|
3354
|
+
})).sort((left, right) => left.name.localeCompare(right.name));
|
|
3355
|
+
for (const index of sortedIndexes) {
|
|
3356
|
+
blocks.push(renderIndex(index));
|
|
3357
|
+
}
|
|
2572
3358
|
if (table.policies?.length) {
|
|
2573
3359
|
for (const policy of table.policies) {
|
|
2574
3360
|
blocks.push(renderPolicy(policy));
|
|
@@ -2586,6 +3372,7 @@ ${blocks.join("\n\n")}
|
|
|
2586
3372
|
var init_schema_to_dsl = __esm({
|
|
2587
3373
|
"node_modules/@xubylele/schema-forge-core/dist/core/sql/schema-to-dsl.js"() {
|
|
2588
3374
|
"use strict";
|
|
3375
|
+
init_normalize();
|
|
2589
3376
|
}
|
|
2590
3377
|
});
|
|
2591
3378
|
|
|
@@ -3039,13 +3826,17 @@ __export(dist_exports, {
|
|
|
3039
3826
|
SchemaValidationError: () => SchemaValidationError,
|
|
3040
3827
|
analyzeSchemaDrift: () => analyzeSchemaDrift,
|
|
3041
3828
|
applySqlOps: () => applySqlOps,
|
|
3829
|
+
buildMigrationPlan: () => buildMigrationPlan,
|
|
3042
3830
|
checkOperationSafety: () => checkOperationSafety,
|
|
3043
3831
|
checkSchemaSafety: () => checkSchemaSafety,
|
|
3044
3832
|
classifyOperation: () => classifyOperation,
|
|
3833
|
+
deterministicIndexName: () => deterministicIndexName,
|
|
3045
3834
|
diffSchemas: () => diffSchemas,
|
|
3046
3835
|
ensureDir: () => ensureDir2,
|
|
3047
3836
|
fileExists: () => fileExists2,
|
|
3048
3837
|
findFiles: () => findFiles,
|
|
3838
|
+
formatMigrationPlanLine: () => formatMigrationPlanLine,
|
|
3839
|
+
formatMigrationPlanLines: () => formatMigrationPlanLines,
|
|
3049
3840
|
generateSql: () => generateSql,
|
|
3050
3841
|
getColumnNamesFromSchema: () => getColumnNamesFromSchema,
|
|
3051
3842
|
getColumnNamesFromState: () => getColumnNamesFromState,
|
|
@@ -3056,6 +3847,7 @@ __export(dist_exports, {
|
|
|
3056
3847
|
getStatePath: () => getStatePath2,
|
|
3057
3848
|
getTableNamesFromSchema: () => getTableNamesFromSchema,
|
|
3058
3849
|
getTableNamesFromState: () => getTableNamesFromState,
|
|
3850
|
+
hashSqlContent: () => hashSqlContent,
|
|
3059
3851
|
introspectPostgresSchema: () => introspectPostgresSchema,
|
|
3060
3852
|
legacyPkName: () => legacyPkName,
|
|
3061
3853
|
legacyUqName: () => legacyUqName,
|
|
@@ -3063,6 +3855,7 @@ __export(dist_exports, {
|
|
|
3063
3855
|
loadState: () => loadState,
|
|
3064
3856
|
normalizeDefault: () => normalizeDefault,
|
|
3065
3857
|
normalizeIdent: () => normalizeIdent,
|
|
3858
|
+
normalizeSqlExpression: () => normalizeSqlExpression,
|
|
3066
3859
|
nowTimestamp: () => nowTimestamp,
|
|
3067
3860
|
parseAddDropConstraint: () => parseAddDropConstraint,
|
|
3068
3861
|
parseAlterColumnType: () => parseAlterColumnType,
|
|
@@ -3101,6 +3894,7 @@ var init_dist = __esm({
|
|
|
3101
3894
|
init_validate();
|
|
3102
3895
|
init_safety();
|
|
3103
3896
|
init_state_manager();
|
|
3897
|
+
init_plan_builder();
|
|
3104
3898
|
init_sql_generator();
|
|
3105
3899
|
init_parse_migration();
|
|
3106
3900
|
init_apply_ops();
|
|
@@ -3126,6 +3920,8 @@ __export(api_exports, {
|
|
|
3126
3920
|
importSchema: () => importSchema,
|
|
3127
3921
|
init: () => init,
|
|
3128
3922
|
introspect: () => introspect,
|
|
3923
|
+
plan: () => plan,
|
|
3924
|
+
preview: () => preview,
|
|
3129
3925
|
validate: () => validate
|
|
3130
3926
|
});
|
|
3131
3927
|
module.exports = __toCommonJS(api_exports);
|
|
@@ -3245,6 +4041,10 @@ async function generateSql2(diff2, provider, config) {
|
|
|
3245
4041
|
const core = await loadCore();
|
|
3246
4042
|
return core.generateSql(diff2, provider, config);
|
|
3247
4043
|
}
|
|
4044
|
+
async function buildMigrationPlan2(diff2) {
|
|
4045
|
+
const core = await loadCore();
|
|
4046
|
+
return core.buildMigrationPlan(diff2);
|
|
4047
|
+
}
|
|
3248
4048
|
async function schemaToState2(schema) {
|
|
3249
4049
|
const core = await loadCore();
|
|
3250
4050
|
return core.schemaToState(schema);
|
|
@@ -3917,12 +4717,109 @@ async function runIntrospect(options = {}) {
|
|
|
3917
4717
|
}
|
|
3918
4718
|
}
|
|
3919
4719
|
|
|
3920
|
-
// src/commands/
|
|
4720
|
+
// src/commands/plan.ts
|
|
3921
4721
|
var import_commander7 = require("commander");
|
|
3922
4722
|
var import_path13 = __toESM(require("path"));
|
|
3923
4723
|
function resolveConfigPath5(root, targetPath) {
|
|
3924
4724
|
return import_path13.default.isAbsolute(targetPath) ? targetPath : import_path13.default.join(root, targetPath);
|
|
3925
4725
|
}
|
|
4726
|
+
async function runPlan(options = {}) {
|
|
4727
|
+
if (options.safe && options.force) {
|
|
4728
|
+
throw new Error("Cannot use --safe and --force flags together. Choose one:\n --safe: Block destructive operations\n --force: Bypass safety checks");
|
|
4729
|
+
}
|
|
4730
|
+
const root = getProjectRoot();
|
|
4731
|
+
const configPath = getConfigPath(root);
|
|
4732
|
+
if (!await fileExists(configPath)) {
|
|
4733
|
+
throw new Error('SchemaForge project not initialized. Run "schema-forge init" first.');
|
|
4734
|
+
}
|
|
4735
|
+
const config = await readJsonFile(configPath, {});
|
|
4736
|
+
const useLiveDatabase = Boolean(options.url || process.env.DATABASE_URL);
|
|
4737
|
+
const requiredFields = useLiveDatabase ? ["schemaFile"] : ["schemaFile", "stateFile"];
|
|
4738
|
+
for (const field of requiredFields) {
|
|
4739
|
+
const value = config[field];
|
|
4740
|
+
if (!value || typeof value !== "string") {
|
|
4741
|
+
throw new Error(`Invalid config: '${field}' is required`);
|
|
4742
|
+
}
|
|
4743
|
+
}
|
|
4744
|
+
const schemaPath = resolveConfigPath5(root, config.schemaFile);
|
|
4745
|
+
const statePath = config.stateFile ? resolveConfigPath5(root, config.stateFile) : null;
|
|
4746
|
+
const schemaSource = await readTextFile(schemaPath);
|
|
4747
|
+
const schema = await parseSchema2(schemaSource);
|
|
4748
|
+
try {
|
|
4749
|
+
await validateSchema2(schema);
|
|
4750
|
+
} catch (error2) {
|
|
4751
|
+
if (error2 instanceof Error) {
|
|
4752
|
+
throw await createSchemaValidationError(error2.message);
|
|
4753
|
+
}
|
|
4754
|
+
throw error2;
|
|
4755
|
+
}
|
|
4756
|
+
const previousState = useLiveDatabase ? await withPostgresQueryExecutor(
|
|
4757
|
+
resolvePostgresConnectionString({ url: options.url }),
|
|
4758
|
+
async (query) => {
|
|
4759
|
+
const schemaFilters = parseSchemaList(options.schema);
|
|
4760
|
+
const liveSchema = await introspectPostgresSchema2({
|
|
4761
|
+
query,
|
|
4762
|
+
...schemaFilters ? { schemas: schemaFilters } : {}
|
|
4763
|
+
});
|
|
4764
|
+
return schemaToState2(liveSchema);
|
|
4765
|
+
}
|
|
4766
|
+
) : await loadState2(statePath ?? "");
|
|
4767
|
+
const diff2 = await diffSchemas2(previousState, schema);
|
|
4768
|
+
if (options.force) {
|
|
4769
|
+
forceWarning("Are you sure to use --force? This option will bypass safety checks for destructive operations.");
|
|
4770
|
+
}
|
|
4771
|
+
if (options.safe && !options.force && diff2.operations.length > 0) {
|
|
4772
|
+
const findings = await validateSchemaChanges2(previousState, schema);
|
|
4773
|
+
const destructiveFindings = findings.filter((f) => f.severity === "error");
|
|
4774
|
+
if (destructiveFindings.length > 0) {
|
|
4775
|
+
const errorMessages = destructiveFindings.map((f) => {
|
|
4776
|
+
const target = f.column ? `${f.table}.${f.column}` : f.table;
|
|
4777
|
+
const typeRange = f.from && f.to ? ` (${f.from} -> ${f.to})` : "";
|
|
4778
|
+
return ` - ${f.code}: ${target}${typeRange}`;
|
|
4779
|
+
}).join("\n");
|
|
4780
|
+
throw await createSchemaValidationError(
|
|
4781
|
+
`Cannot proceed with --safe flag: Found ${destructiveFindings.length} destructive operation(s):
|
|
4782
|
+
${errorMessages}
|
|
4783
|
+
|
|
4784
|
+
Remove --safe flag or modify schema to avoid destructive changes.`
|
|
4785
|
+
);
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
if (!options.safe && !options.force && diff2.operations.length > 0) {
|
|
4789
|
+
const findings = await validateSchemaChanges2(previousState, schema);
|
|
4790
|
+
const riskyFindings = findings.filter((f) => f.severity === "error" || f.severity === "warning");
|
|
4791
|
+
if (riskyFindings.length > 0) {
|
|
4792
|
+
const confirmed = await confirmDestructiveOps(findings);
|
|
4793
|
+
if (!confirmed) {
|
|
4794
|
+
if (process.exitCode !== EXIT_CODES.CI_DESTRUCTIVE) {
|
|
4795
|
+
process.exitCode = EXIT_CODES.VALIDATION_ERROR;
|
|
4796
|
+
}
|
|
4797
|
+
return;
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4800
|
+
}
|
|
4801
|
+
if (diff2.operations.length === 0) {
|
|
4802
|
+
success("No changes detected");
|
|
4803
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
4804
|
+
return;
|
|
4805
|
+
}
|
|
4806
|
+
const plan2 = await buildMigrationPlan2(diff2);
|
|
4807
|
+
console.log(plan2.lines.join("\n"));
|
|
4808
|
+
process.exitCode = EXIT_CODES.SUCCESS;
|
|
4809
|
+
}
|
|
4810
|
+
|
|
4811
|
+
// src/commands/preview.ts
|
|
4812
|
+
var import_commander8 = require("commander");
|
|
4813
|
+
async function runPreview(options = {}) {
|
|
4814
|
+
await runPlan(options);
|
|
4815
|
+
}
|
|
4816
|
+
|
|
4817
|
+
// src/commands/validate.ts
|
|
4818
|
+
var import_commander9 = require("commander");
|
|
4819
|
+
var import_path14 = __toESM(require("path"));
|
|
4820
|
+
function resolveConfigPath6(root, targetPath) {
|
|
4821
|
+
return import_path14.default.isAbsolute(targetPath) ? targetPath : import_path14.default.join(root, targetPath);
|
|
4822
|
+
}
|
|
3926
4823
|
async function runValidate(options = {}) {
|
|
3927
4824
|
const root = getProjectRoot();
|
|
3928
4825
|
const configPath = getConfigPath(root);
|
|
@@ -3938,11 +4835,11 @@ async function runValidate(options = {}) {
|
|
|
3938
4835
|
throw new Error(`Invalid config: '${field}' is required`);
|
|
3939
4836
|
}
|
|
3940
4837
|
}
|
|
3941
|
-
const schemaPath =
|
|
4838
|
+
const schemaPath = resolveConfigPath6(root, config.schemaFile);
|
|
3942
4839
|
if (!config.stateFile) {
|
|
3943
4840
|
throw new Error("Invalid config: 'stateFile' is required");
|
|
3944
4841
|
}
|
|
3945
|
-
const statePath =
|
|
4842
|
+
const statePath = resolveConfigPath6(root, config.stateFile);
|
|
3946
4843
|
const schemaSource = await readTextFile(schemaPath);
|
|
3947
4844
|
const schema = await parseSchema2(schemaSource);
|
|
3948
4845
|
try {
|
|
@@ -4054,6 +4951,12 @@ async function doctor(options = {}) {
|
|
|
4054
4951
|
async function validate(options = {}) {
|
|
4055
4952
|
return runWithResult(() => runValidate(options));
|
|
4056
4953
|
}
|
|
4954
|
+
async function plan(options = {}) {
|
|
4955
|
+
return runWithResult(() => runPlan(options));
|
|
4956
|
+
}
|
|
4957
|
+
async function preview(options = {}) {
|
|
4958
|
+
return runWithResult(() => runPreview(options));
|
|
4959
|
+
}
|
|
4057
4960
|
async function introspect(options = {}) {
|
|
4058
4961
|
return runWithResult(() => runIntrospect(options));
|
|
4059
4962
|
}
|
|
@@ -4069,5 +4972,7 @@ async function importSchema(inputPath, options = {}) {
|
|
|
4069
4972
|
importSchema,
|
|
4070
4973
|
init,
|
|
4071
4974
|
introspect,
|
|
4975
|
+
plan,
|
|
4976
|
+
preview,
|
|
4072
4977
|
validate
|
|
4073
4978
|
});
|