@xubylele/schema-forge-core 1.0.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/LICENSE +15 -0
- package/README.md +254 -0
- package/dist/core/diff.d.ts +22 -0
- package/dist/core/diff.d.ts.map +1 -0
- package/dist/core/diff.js +248 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/errors.d.ts +4 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +7 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/fs.d.ts +31 -0
- package/dist/core/fs.d.ts.map +1 -0
- package/dist/core/fs.js +104 -0
- package/dist/core/fs.js.map +1 -0
- package/dist/core/normalize.d.ts +10 -0
- package/dist/core/normalize.d.ts.map +1 -0
- package/dist/core/normalize.js +152 -0
- package/dist/core/normalize.js.map +1 -0
- package/dist/core/parser.d.ts +25 -0
- package/dist/core/parser.d.ts.map +1 -0
- package/dist/core/parser.js +210 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core/paths.d.ts +29 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +41 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/sql/apply-ops.d.ts +3 -0
- package/dist/core/sql/apply-ops.d.ts.map +1 -0
- package/dist/core/sql/apply-ops.js +174 -0
- package/dist/core/sql/apply-ops.js.map +1 -0
- package/dist/core/sql/load-migrations.d.ts +6 -0
- package/dist/core/sql/load-migrations.d.ts.map +1 -0
- package/dist/core/sql/load-migrations.js +26 -0
- package/dist/core/sql/load-migrations.js.map +1 -0
- package/dist/core/sql/parse-migration.d.ts +11 -0
- package/dist/core/sql/parse-migration.d.ts.map +1 -0
- package/dist/core/sql/parse-migration.js +509 -0
- package/dist/core/sql/parse-migration.js.map +1 -0
- package/dist/core/sql/schema-to-dsl.d.ts +3 -0
- package/dist/core/sql/schema-to-dsl.d.ts.map +1 -0
- package/dist/core/sql/schema-to-dsl.js +33 -0
- package/dist/core/sql/schema-to-dsl.js.map +1 -0
- package/dist/core/sql/split-statements.d.ts +11 -0
- package/dist/core/sql/split-statements.d.ts.map +1 -0
- package/dist/core/sql/split-statements.js +111 -0
- package/dist/core/sql/split-statements.js.map +1 -0
- package/dist/core/state-manager.d.ts +17 -0
- package/dist/core/state-manager.d.ts.map +1 -0
- package/dist/core/state-manager.js +48 -0
- package/dist/core/state-manager.js.map +1 -0
- package/dist/core/utils.d.ts +30 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +45 -0
- package/dist/core/utils.js.map +1 -0
- package/dist/core/validate.d.ts +20 -0
- package/dist/core/validate.d.ts.map +1 -0
- package/dist/core/validate.js +154 -0
- package/dist/core/validate.js.map +1 -0
- package/dist/core/validator.d.ts +16 -0
- package/dist/core/validator.d.ts.map +1 -0
- package/dist/core/validator.js +126 -0
- package/dist/core/validator.js.map +1 -0
- package/dist/diff/diff.d.ts +7 -0
- package/dist/diff/diff.d.ts.map +1 -0
- package/dist/diff/diff.js +75 -0
- package/dist/diff/diff.js.map +1 -0
- package/dist/generator/sql-generator.d.ts +8 -0
- package/dist/generator/sql-generator.d.ts.map +1 -0
- package/dist/generator/sql-generator.js +126 -0
- package/dist/generator/sql-generator.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/parser.d.ts +3 -0
- package/dist/parser/parser.d.ts.map +1 -0
- package/dist/parser/parser.js +135 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/state/snapshot.d.ts +3 -0
- package/dist/state/snapshot.d.ts.map +1 -0
- package/dist/state/snapshot.js +22 -0
- package/dist/state/snapshot.js.map +1 -0
- package/package.json +33 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split SQL text into statements by semicolon.
|
|
3
|
+
*
|
|
4
|
+
* Handles semicolons inside:
|
|
5
|
+
* - single-quoted strings
|
|
6
|
+
* - double-quoted identifiers
|
|
7
|
+
* - line/block comments
|
|
8
|
+
* - dollar-quoted blocks
|
|
9
|
+
*/
|
|
10
|
+
export function splitSqlStatements(sql) {
|
|
11
|
+
const statements = [];
|
|
12
|
+
let current = '';
|
|
13
|
+
let inSingleQuote = false;
|
|
14
|
+
let inDoubleQuote = false;
|
|
15
|
+
let inLineComment = false;
|
|
16
|
+
let inBlockComment = false;
|
|
17
|
+
let dollarTag = null;
|
|
18
|
+
let index = 0;
|
|
19
|
+
while (index < sql.length) {
|
|
20
|
+
const char = sql[index];
|
|
21
|
+
const next = index + 1 < sql.length ? sql[index + 1] : '';
|
|
22
|
+
if (inLineComment) {
|
|
23
|
+
current += char;
|
|
24
|
+
if (char === '\n') {
|
|
25
|
+
inLineComment = false;
|
|
26
|
+
}
|
|
27
|
+
index++;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (inBlockComment) {
|
|
31
|
+
current += char;
|
|
32
|
+
if (char === '*' && next === '/') {
|
|
33
|
+
current += next;
|
|
34
|
+
inBlockComment = false;
|
|
35
|
+
index += 2;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
index++;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (!inSingleQuote && !inDoubleQuote && dollarTag === null) {
|
|
42
|
+
if (char === '-' && next === '-') {
|
|
43
|
+
current += char + next;
|
|
44
|
+
inLineComment = true;
|
|
45
|
+
index += 2;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (char === '/' && next === '*') {
|
|
49
|
+
current += char + next;
|
|
50
|
+
inBlockComment = true;
|
|
51
|
+
index += 2;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (!inDoubleQuote && dollarTag === null && char === "'") {
|
|
56
|
+
current += char;
|
|
57
|
+
if (inSingleQuote && next === "'") {
|
|
58
|
+
current += next;
|
|
59
|
+
index += 2;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
inSingleQuote = !inSingleQuote;
|
|
63
|
+
index++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (!inSingleQuote && dollarTag === null && char === '"') {
|
|
67
|
+
current += char;
|
|
68
|
+
if (inDoubleQuote && next === '"') {
|
|
69
|
+
current += next;
|
|
70
|
+
index += 2;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
inDoubleQuote = !inDoubleQuote;
|
|
74
|
+
index++;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (!inSingleQuote && !inDoubleQuote) {
|
|
78
|
+
if (dollarTag === null && char === '$') {
|
|
79
|
+
const remainder = sql.slice(index);
|
|
80
|
+
const match = remainder.match(/^\$[a-zA-Z_][a-zA-Z0-9_]*\$|^\$\$/);
|
|
81
|
+
if (match) {
|
|
82
|
+
dollarTag = match[0];
|
|
83
|
+
current += match[0];
|
|
84
|
+
index += match[0].length;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (dollarTag !== null && sql.startsWith(dollarTag, index)) {
|
|
89
|
+
current += dollarTag;
|
|
90
|
+
index += dollarTag.length;
|
|
91
|
+
dollarTag = null;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (!inSingleQuote && !inDoubleQuote && dollarTag === null && char === ';') {
|
|
96
|
+
if (current.trim().length > 0) {
|
|
97
|
+
statements.push(current.trim());
|
|
98
|
+
}
|
|
99
|
+
current = '';
|
|
100
|
+
index++;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
current += char;
|
|
104
|
+
index++;
|
|
105
|
+
}
|
|
106
|
+
if (current.trim().length > 0) {
|
|
107
|
+
statements.push(current.trim());
|
|
108
|
+
}
|
|
109
|
+
return statements;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=split-statements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split-statements.js","sourceRoot":"","sources":["../../../src/core/sql/split-statements.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DatabaseSchema, StateFile } from '../types/schema';
|
|
2
|
+
/**
|
|
3
|
+
* Convert a DatabaseSchema to a StateFile
|
|
4
|
+
* Transforms table columns from array to indexed Record format
|
|
5
|
+
*/
|
|
6
|
+
export declare function schemaToState(schema: DatabaseSchema): Promise<StateFile>;
|
|
7
|
+
/**
|
|
8
|
+
* Load state from disk
|
|
9
|
+
* Returns fallback { version: 1, tables: {} } if file doesn't exist
|
|
10
|
+
*/
|
|
11
|
+
export declare function loadState(statePath: string): Promise<StateFile>;
|
|
12
|
+
/**
|
|
13
|
+
* Save state to disk
|
|
14
|
+
* Creates parent directories automatically if they don't exist
|
|
15
|
+
*/
|
|
16
|
+
export declare function saveState(statePath: string, state: StateFile): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=state-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-manager.d.ts","sourceRoot":"","sources":["../../src/core/state-manager.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EAEd,SAAS,EAEV,MAAM,iBAAiB,CAAC;AAGzB;;;GAGG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,SAAS,CAAC,CA6B9E;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAErE;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAIlF"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { ensureDir, fileExists, readJsonFile, writeJsonFile } from './fs';
|
|
3
|
+
/**
|
|
4
|
+
* Convert a DatabaseSchema to a StateFile
|
|
5
|
+
* Transforms table columns from array to indexed Record format
|
|
6
|
+
*/
|
|
7
|
+
export async function schemaToState(schema) {
|
|
8
|
+
const tables = {};
|
|
9
|
+
for (const [tableName, table] of Object.entries(schema.tables)) {
|
|
10
|
+
const columns = {};
|
|
11
|
+
const primaryKeyColumn = table.primaryKey ?? table.columns.find(column => column.primaryKey)?.name ?? null;
|
|
12
|
+
for (const column of table.columns) {
|
|
13
|
+
columns[column.name] = {
|
|
14
|
+
type: column.type,
|
|
15
|
+
...(column.primaryKey !== undefined && { primaryKey: column.primaryKey }),
|
|
16
|
+
...(column.unique !== undefined && { unique: column.unique }),
|
|
17
|
+
nullable: column.nullable ?? true,
|
|
18
|
+
...(column.default !== undefined && { default: column.default }),
|
|
19
|
+
...(column.foreignKey !== undefined && { foreignKey: column.foreignKey }),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
tables[tableName] = {
|
|
23
|
+
columns,
|
|
24
|
+
...(primaryKeyColumn !== null && { primaryKey: primaryKeyColumn }),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
version: 1,
|
|
29
|
+
tables,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Load state from disk
|
|
34
|
+
* Returns fallback { version: 1, tables: {} } if file doesn't exist
|
|
35
|
+
*/
|
|
36
|
+
export async function loadState(statePath) {
|
|
37
|
+
return await readJsonFile(statePath, { version: 1, tables: {} });
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Save state to disk
|
|
41
|
+
* Creates parent directories automatically if they don't exist
|
|
42
|
+
*/
|
|
43
|
+
export async function saveState(statePath, state) {
|
|
44
|
+
const dirPath = path.dirname(statePath);
|
|
45
|
+
await ensureDir(dirPath);
|
|
46
|
+
await writeJsonFile(statePath, state);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=state-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-manager.js","sourceRoot":"","sources":["../../src/core/state-manager.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAOxB,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AAE1E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAsB;IACxD,MAAM,MAAM,GAA+B,EAAE,CAAC;IAE9C,KAAK,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/D,MAAM,OAAO,GAAgC,EAAE,CAAC;QAChD,MAAM,gBAAgB,GACpB,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC;QAEpF,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACnC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG;gBACrB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,GAAG,CAAC,MAAM,CAAC,UAAU,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;gBACzE,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;gBAC7D,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,IAAI;gBACjC,GAAG,CAAC,MAAM,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;gBAChE,GAAG,CAAC,MAAM,CAAC,UAAU,KAAK,SAAS,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;aAC1E,CAAC;QACJ,CAAC;QAED,MAAM,CAAC,SAAS,CAAC,GAAG;YAClB,OAAO;YACP,GAAG,CAAC,gBAAgB,KAAK,IAAI,IAAI,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE,CAAC;QACV,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,SAAiB;IAC/C,OAAO,MAAM,YAAY,CAAY,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,SAAiB,EAAE,KAAgB;IACjE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACxC,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;IACzB,MAAM,aAAa,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a timestamp string in the format YYYYMMDDHHmmss
|
|
3
|
+
* using the local time zone.
|
|
4
|
+
*
|
|
5
|
+
* @returns A 14-character timestamp string (e.g., "20260222143045")
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* const timestamp = nowTimestamp();
|
|
9
|
+
* // Returns: "20260222143045"
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare function nowTimestamp(): string;
|
|
13
|
+
/**
|
|
14
|
+
* Converts a string to a safe kebab-case format suitable for file names.
|
|
15
|
+
* Trims whitespace, converts to lowercase, replaces non-alphanumeric characters
|
|
16
|
+
* with hyphens, and removes leading/trailing hyphens.
|
|
17
|
+
*
|
|
18
|
+
* @param name - The string to convert to kebab-case
|
|
19
|
+
* @returns A kebab-case string, or 'migration' if the result is empty
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* slugifyName("MyFileName"); // Returns: "my-file-name"
|
|
23
|
+
* slugifyName("My File Name"); // Returns: "my-file-name"
|
|
24
|
+
* slugifyName("My_File@#$Name"); // Returns: "my-file-name"
|
|
25
|
+
* slugifyName(""); // Returns: "migration"
|
|
26
|
+
* slugifyName(" "); // Returns: "migration"
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function slugifyName(name: string): string;
|
|
30
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/core/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAYrC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQhD"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a timestamp string in the format YYYYMMDDHHmmss
|
|
3
|
+
* using the local time zone.
|
|
4
|
+
*
|
|
5
|
+
* @returns A 14-character timestamp string (e.g., "20260222143045")
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* const timestamp = nowTimestamp();
|
|
9
|
+
* // Returns: "20260222143045"
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export function nowTimestamp() {
|
|
13
|
+
const date = new Date();
|
|
14
|
+
const pad = (value) => String(value).padStart(2, '0');
|
|
15
|
+
return (String(date.getFullYear()) +
|
|
16
|
+
pad(date.getMonth() + 1) +
|
|
17
|
+
pad(date.getDate()) +
|
|
18
|
+
pad(date.getHours()) +
|
|
19
|
+
pad(date.getMinutes()) +
|
|
20
|
+
pad(date.getSeconds()));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Converts a string to a safe kebab-case format suitable for file names.
|
|
24
|
+
* Trims whitespace, converts to lowercase, replaces non-alphanumeric characters
|
|
25
|
+
* with hyphens, and removes leading/trailing hyphens.
|
|
26
|
+
*
|
|
27
|
+
* @param name - The string to convert to kebab-case
|
|
28
|
+
* @returns A kebab-case string, or 'migration' if the result is empty
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* slugifyName("MyFileName"); // Returns: "my-file-name"
|
|
32
|
+
* slugifyName("My File Name"); // Returns: "my-file-name"
|
|
33
|
+
* slugifyName("My_File@#$Name"); // Returns: "my-file-name"
|
|
34
|
+
* slugifyName(""); // Returns: "migration"
|
|
35
|
+
* slugifyName(" "); // Returns: "migration"
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function slugifyName(name) {
|
|
39
|
+
return (name
|
|
40
|
+
.trim()
|
|
41
|
+
.toLowerCase()
|
|
42
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
43
|
+
.replace(/^-+|-+$/g, '')) || 'migration';
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/core/utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;IACxB,MAAM,GAAG,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAEtE,OAAO,CACL,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAC1B,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACxB,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACnB,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACpB,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;QACtB,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CACvB,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,CACL,IAAI;SACD,IAAI,EAAE;SACN,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAC3B,IAAI,WAAW,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DatabaseSchema, StateFile } from '../types/schema';
|
|
2
|
+
export type Severity = 'error' | 'warning';
|
|
3
|
+
export interface Finding {
|
|
4
|
+
severity: Severity;
|
|
5
|
+
code: 'DROP_TABLE' | 'DROP_COLUMN' | 'ALTER_COLUMN_TYPE' | 'SET_NOT_NULL';
|
|
6
|
+
table: string;
|
|
7
|
+
column?: string;
|
|
8
|
+
from?: string;
|
|
9
|
+
to?: string;
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ValidationReport {
|
|
13
|
+
hasErrors: boolean;
|
|
14
|
+
hasWarnings: boolean;
|
|
15
|
+
errors: Array<Omit<Finding, 'severity'>>;
|
|
16
|
+
warnings: Array<Omit<Finding, 'severity'>>;
|
|
17
|
+
}
|
|
18
|
+
export declare function validateSchemaChanges(previousState: StateFile, currentSchema: DatabaseSchema): Finding[];
|
|
19
|
+
export declare function toValidationReport(findings: Finding[]): ValidationReport;
|
|
20
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/core/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjE,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3C,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,YAAY,GAAG,aAAa,GAAG,mBAAmB,GAAG,cAAc,CAAC;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;IACzC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;CAC5C;AAgHD,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,GAAG,OAAO,EAAE,CAyDxG;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,gBAAgB,CAUxE"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { diffSchemas } from './diff';
|
|
2
|
+
function normalizeColumnType(type) {
|
|
3
|
+
return type
|
|
4
|
+
.toLowerCase()
|
|
5
|
+
.trim()
|
|
6
|
+
.replace(/\s+/g, ' ')
|
|
7
|
+
.replace(/\s*\(\s*/g, '(')
|
|
8
|
+
.replace(/\s*,\s*/g, ',')
|
|
9
|
+
.replace(/\s*\)\s*/g, ')');
|
|
10
|
+
}
|
|
11
|
+
function parseVarcharLength(type) {
|
|
12
|
+
const match = normalizeColumnType(type).match(/^varchar\((\d+)\)$/);
|
|
13
|
+
return match ? Number(match[1]) : null;
|
|
14
|
+
}
|
|
15
|
+
function parseNumericType(type) {
|
|
16
|
+
const match = normalizeColumnType(type).match(/^numeric\((\d+),(\d+)\)$/);
|
|
17
|
+
if (!match) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
precision: Number(match[1]),
|
|
22
|
+
scale: Number(match[2]),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function classifyTypeChange(from, to) {
|
|
26
|
+
const fromType = normalizeColumnType(from);
|
|
27
|
+
const toType = normalizeColumnType(to);
|
|
28
|
+
const uuidInvolved = fromType === 'uuid' || toType === 'uuid';
|
|
29
|
+
if (uuidInvolved && fromType !== toType) {
|
|
30
|
+
return {
|
|
31
|
+
severity: 'error',
|
|
32
|
+
message: `Type changed from ${fromType} to ${toType} (likely incompatible cast)`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (fromType === 'int' && toType === 'bigint') {
|
|
36
|
+
return {
|
|
37
|
+
severity: 'warning',
|
|
38
|
+
message: 'Type widened from int to bigint',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (fromType === 'bigint' && toType === 'int') {
|
|
42
|
+
return {
|
|
43
|
+
severity: 'error',
|
|
44
|
+
message: 'Type narrowed from bigint to int (likely incompatible cast)',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (fromType === 'text' && parseVarcharLength(toType) !== null) {
|
|
48
|
+
return {
|
|
49
|
+
severity: 'error',
|
|
50
|
+
message: `Type changed from text to ${toType} (may truncate existing values)`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (parseVarcharLength(fromType) !== null && toType === 'text') {
|
|
54
|
+
return {
|
|
55
|
+
severity: 'warning',
|
|
56
|
+
message: 'Type widened from varchar(n) to text',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const fromVarcharLength = parseVarcharLength(fromType);
|
|
60
|
+
const toVarcharLength = parseVarcharLength(toType);
|
|
61
|
+
if (fromVarcharLength !== null && toVarcharLength !== null) {
|
|
62
|
+
if (toVarcharLength >= fromVarcharLength) {
|
|
63
|
+
return {
|
|
64
|
+
severity: 'warning',
|
|
65
|
+
message: `Type widened from varchar(${fromVarcharLength}) to varchar(${toVarcharLength})`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
severity: 'error',
|
|
70
|
+
message: `Type narrowed from varchar(${fromVarcharLength}) to varchar(${toVarcharLength})`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const fromNumeric = parseNumericType(fromType);
|
|
74
|
+
const toNumeric = parseNumericType(toType);
|
|
75
|
+
if (fromNumeric && toNumeric && fromNumeric.scale === toNumeric.scale) {
|
|
76
|
+
if (toNumeric.precision >= fromNumeric.precision) {
|
|
77
|
+
return {
|
|
78
|
+
severity: 'warning',
|
|
79
|
+
message: `Type widened from numeric(${fromNumeric.precision},${fromNumeric.scale}) to numeric(${toNumeric.precision},${toNumeric.scale})`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
severity: 'error',
|
|
84
|
+
message: `Type narrowed from numeric(${fromNumeric.precision},${fromNumeric.scale}) to numeric(${toNumeric.precision},${toNumeric.scale})`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
severity: 'warning',
|
|
89
|
+
message: `Type changed from ${fromType} to ${toType} (compatibility unknown)`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export function validateSchemaChanges(previousState, currentSchema) {
|
|
93
|
+
const findings = [];
|
|
94
|
+
const diff = diffSchemas(previousState, currentSchema);
|
|
95
|
+
for (const operation of diff.operations) {
|
|
96
|
+
switch (operation.kind) {
|
|
97
|
+
case 'drop_table':
|
|
98
|
+
findings.push({
|
|
99
|
+
severity: 'error',
|
|
100
|
+
code: 'DROP_TABLE',
|
|
101
|
+
table: operation.tableName,
|
|
102
|
+
message: 'Table removed',
|
|
103
|
+
});
|
|
104
|
+
break;
|
|
105
|
+
case 'drop_column':
|
|
106
|
+
findings.push({
|
|
107
|
+
severity: 'error',
|
|
108
|
+
code: 'DROP_COLUMN',
|
|
109
|
+
table: operation.tableName,
|
|
110
|
+
column: operation.columnName,
|
|
111
|
+
message: 'Column removed',
|
|
112
|
+
});
|
|
113
|
+
break;
|
|
114
|
+
case 'column_type_changed': {
|
|
115
|
+
const classification = classifyTypeChange(operation.fromType, operation.toType);
|
|
116
|
+
findings.push({
|
|
117
|
+
severity: classification.severity,
|
|
118
|
+
code: 'ALTER_COLUMN_TYPE',
|
|
119
|
+
table: operation.tableName,
|
|
120
|
+
column: operation.columnName,
|
|
121
|
+
from: normalizeColumnType(operation.fromType),
|
|
122
|
+
to: normalizeColumnType(operation.toType),
|
|
123
|
+
message: classification.message,
|
|
124
|
+
});
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
case 'column_nullability_changed':
|
|
128
|
+
if (operation.from && !operation.to) {
|
|
129
|
+
findings.push({
|
|
130
|
+
severity: 'warning',
|
|
131
|
+
code: 'SET_NOT_NULL',
|
|
132
|
+
table: operation.tableName,
|
|
133
|
+
column: operation.columnName,
|
|
134
|
+
message: 'Column changed to NOT NULL (may fail if data contains NULLs)',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
default:
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return findings;
|
|
143
|
+
}
|
|
144
|
+
export function toValidationReport(findings) {
|
|
145
|
+
const errors = findings.filter(finding => finding.severity === 'error');
|
|
146
|
+
const warnings = findings.filter(finding => finding.severity === 'warning');
|
|
147
|
+
return {
|
|
148
|
+
hasErrors: errors.length > 0,
|
|
149
|
+
hasWarnings: warnings.length > 0,
|
|
150
|
+
errors: errors.map(({ severity, ...finding }) => finding),
|
|
151
|
+
warnings: warnings.map(({ severity, ...finding }) => finding),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/core/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DatabaseSchema } from '../types/schema';
|
|
2
|
+
/**
|
|
3
|
+
* Validates a DatabaseSchema structure and throws an Error when validations fail
|
|
4
|
+
*
|
|
5
|
+
* Validations:
|
|
6
|
+
* - Detects duplicate tables
|
|
7
|
+
* - Detects duplicate columns within each table
|
|
8
|
+
* - Detects multiple primary keys in a table
|
|
9
|
+
* - Validates that column types are valid
|
|
10
|
+
* - Validates that foreign keys reference existing tables and columns
|
|
11
|
+
*
|
|
12
|
+
* @param schema - The DatabaseSchema to validate
|
|
13
|
+
* @throws Error with a descriptive message if validation rules are violated
|
|
14
|
+
*/
|
|
15
|
+
export declare function validateSchema(schema: DatabaseSchema): void;
|
|
16
|
+
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/core/validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA6B,MAAM,iBAAiB,CAAC;AAgCjF;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAQ3D"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valid column types for the database
|
|
3
|
+
*/
|
|
4
|
+
const VALID_BASE_COLUMN_TYPES = [
|
|
5
|
+
'uuid',
|
|
6
|
+
'varchar',
|
|
7
|
+
'text',
|
|
8
|
+
'int',
|
|
9
|
+
'bigint',
|
|
10
|
+
'boolean',
|
|
11
|
+
'timestamptz',
|
|
12
|
+
'date',
|
|
13
|
+
];
|
|
14
|
+
function isValidColumnType(type) {
|
|
15
|
+
const normalizedType = type
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.trim()
|
|
18
|
+
.replace(/\s+/g, ' ')
|
|
19
|
+
.replace(/\s*\(\s*/g, '(')
|
|
20
|
+
.replace(/\s*,\s*/g, ',')
|
|
21
|
+
.replace(/\s*\)\s*/g, ')');
|
|
22
|
+
if (VALID_BASE_COLUMN_TYPES.includes(normalizedType)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return /^varchar\(\d+\)$/.test(normalizedType) || /^numeric\(\d+,\d+\)$/.test(normalizedType);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Validates a DatabaseSchema structure and throws an Error when validations fail
|
|
29
|
+
*
|
|
30
|
+
* Validations:
|
|
31
|
+
* - Detects duplicate tables
|
|
32
|
+
* - Detects duplicate columns within each table
|
|
33
|
+
* - Detects multiple primary keys in a table
|
|
34
|
+
* - Validates that column types are valid
|
|
35
|
+
* - Validates that foreign keys reference existing tables and columns
|
|
36
|
+
*
|
|
37
|
+
* @param schema - The DatabaseSchema to validate
|
|
38
|
+
* @throws Error with a descriptive message if validation rules are violated
|
|
39
|
+
*/
|
|
40
|
+
export function validateSchema(schema) {
|
|
41
|
+
validateDuplicateTables(schema);
|
|
42
|
+
// Validate each table
|
|
43
|
+
for (const tableName in schema.tables) {
|
|
44
|
+
const table = schema.tables[tableName];
|
|
45
|
+
validateTableColumns(tableName, table, schema.tables);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Validates that there are no duplicate tables in the schema
|
|
50
|
+
*
|
|
51
|
+
* @param schema - The DatabaseSchema to validate
|
|
52
|
+
* @throws Error if duplicate tables are detected
|
|
53
|
+
*/
|
|
54
|
+
function validateDuplicateTables(schema) {
|
|
55
|
+
const tableNames = Object.keys(schema.tables);
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
for (const tableName of tableNames) {
|
|
58
|
+
if (seen.has(tableName)) {
|
|
59
|
+
throw new Error(`Duplicate table: '${tableName}'`);
|
|
60
|
+
}
|
|
61
|
+
seen.add(tableName);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Validates columns, primary keys, types, and foreign keys within a table
|
|
66
|
+
*
|
|
67
|
+
* @param tableName - Table name
|
|
68
|
+
* @param table - The table to validate
|
|
69
|
+
* @param allTables - All schema tables (used to validate foreign keys)
|
|
70
|
+
* @throws Error if validation violations are detected
|
|
71
|
+
*/
|
|
72
|
+
function validateTableColumns(tableName, table, allTables) {
|
|
73
|
+
// Validate duplicate columns
|
|
74
|
+
const columnNames = new Set();
|
|
75
|
+
const primaryKeyColumns = [];
|
|
76
|
+
for (const column of table.columns) {
|
|
77
|
+
// Check for duplicate columns
|
|
78
|
+
if (columnNames.has(column.name)) {
|
|
79
|
+
throw new Error(`Table '${tableName}': duplicate column '${column.name}'`);
|
|
80
|
+
}
|
|
81
|
+
columnNames.add(column.name);
|
|
82
|
+
// Count primary keys
|
|
83
|
+
if (column.primaryKey) {
|
|
84
|
+
primaryKeyColumns.push(column.name);
|
|
85
|
+
}
|
|
86
|
+
// Validate column type
|
|
87
|
+
if (!isValidColumnType(column.type)) {
|
|
88
|
+
throw new Error(`Table '${tableName}', column '${column.name}': type '${column.type}' is not valid. Supported types: ${VALID_BASE_COLUMN_TYPES.join(', ')}, varchar(n), numeric(p,s)`);
|
|
89
|
+
}
|
|
90
|
+
// Validate foreign key
|
|
91
|
+
if (column.foreignKey) {
|
|
92
|
+
const fkTable = column.foreignKey.table;
|
|
93
|
+
const fkColumn = column.foreignKey.column;
|
|
94
|
+
// Check that the referenced table exists
|
|
95
|
+
if (!allTables[fkTable]) {
|
|
96
|
+
throw new Error(`Table '${tableName}', column '${column.name}': referenced table '${fkTable}' does not exist`);
|
|
97
|
+
}
|
|
98
|
+
// Check that the column exists in the referenced table
|
|
99
|
+
const referencedTable = allTables[fkTable];
|
|
100
|
+
const columnExists = referencedTable.columns.some(col => col.name === fkColumn);
|
|
101
|
+
if (!columnExists) {
|
|
102
|
+
throw new Error(`Table '${tableName}', column '${column.name}': table '${fkTable}' does not have column '${fkColumn}'`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Validate multiple primary keys
|
|
107
|
+
if (primaryKeyColumns.length > 1) {
|
|
108
|
+
throw new Error(`Table '${tableName}': can only have one primary key (found ${primaryKeyColumns.length})`);
|
|
109
|
+
}
|
|
110
|
+
const normalizedPrimaryKey = table.primaryKey ?? primaryKeyColumns[0] ?? null;
|
|
111
|
+
if (table.primaryKey && !columnNames.has(table.primaryKey)) {
|
|
112
|
+
throw new Error(`Table '${tableName}': primary key column '${table.primaryKey}' does not exist`);
|
|
113
|
+
}
|
|
114
|
+
if (table.primaryKey &&
|
|
115
|
+
primaryKeyColumns.length === 1 &&
|
|
116
|
+
primaryKeyColumns[0] !== table.primaryKey) {
|
|
117
|
+
throw new Error(`Table '${tableName}': column-level primary key '${primaryKeyColumns[0]}' does not match table primary key '${table.primaryKey}'`);
|
|
118
|
+
}
|
|
119
|
+
if (normalizedPrimaryKey) {
|
|
120
|
+
const pkMatches = table.columns.filter(column => column.name === normalizedPrimaryKey);
|
|
121
|
+
if (pkMatches.length !== 1) {
|
|
122
|
+
throw new Error(`Table '${tableName}': primary key column '${normalizedPrimaryKey}' is invalid`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/core/validator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Column, DatabaseSchema, DiffResult, StateColumn, StateFile } from '../types/schema';
|
|
2
|
+
export declare function getTableNamesFromState(state: StateFile): Set<string>;
|
|
3
|
+
export declare function getTableNamesFromSchema(schema: DatabaseSchema): Set<string>;
|
|
4
|
+
export declare function getColumnNamesFromState(stateColumns: Record<string, StateColumn>): Set<string>;
|
|
5
|
+
export declare function getColumnNamesFromSchema(dbColumns: Column[]): Set<string>;
|
|
6
|
+
export declare function diffSchemas(oldSnapshot: StateFile, newSchema: DatabaseSchema): DiffResult;
|
|
7
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/diff/diff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,EACN,cAAc,EACd,UAAU,EAEV,WAAW,EACX,SAAS,EACV,MAAM,iBAAiB,CAAC;AAEzB,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,CAEpE;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,cAAc,GAAG,GAAG,CAAC,MAAM,CAAC,CAE3E;AAED,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GACxC,GAAG,CAAC,MAAM,CAAC,CAEb;AAED,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,CAEzE;AAMD,wBAAgB,WAAW,CAAC,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,GAAG,UAAU,CA0EzF"}
|