zenstack-kit 0.1.1 → 0.1.3
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/dist/cli/app.d.ts.map +1 -1
- package/dist/cli/app.js +9 -28
- package/dist/cli/commands.d.ts +3 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +34 -1
- package/dist/cli/index.js +0 -0
- package/dist/cli/prompts.d.ts +9 -0
- package/dist/cli/prompts.d.ts.map +1 -1
- package/dist/cli/prompts.js +57 -1
- package/dist/migrations/prisma.d.ts +10 -0
- package/dist/migrations/prisma.d.ts.map +1 -1
- package/dist/migrations/prisma.js +85 -3
- package/package.json +1 -5
- package/dist/cli.d.ts +0 -12
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -240
- package/dist/config-loader.d.ts +0 -6
- package/dist/config-loader.d.ts.map +0 -1
- package/dist/config-loader.js +0 -36
- package/dist/config.d.ts +0 -62
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -44
- package/dist/init-prompts.d.ts +0 -13
- package/dist/init-prompts.d.ts.map +0 -1
- package/dist/init-prompts.js +0 -64
- package/dist/introspect.d.ts +0 -54
- package/dist/introspect.d.ts.map +0 -1
- package/dist/introspect.js +0 -75
- package/dist/kysely-adapter.d.ts +0 -49
- package/dist/kysely-adapter.d.ts.map +0 -1
- package/dist/kysely-adapter.js +0 -74
- package/dist/migrate-apply.d.ts +0 -18
- package/dist/migrate-apply.d.ts.map +0 -1
- package/dist/migrate-apply.js +0 -61
- package/dist/migrations.d.ts +0 -161
- package/dist/migrations.d.ts.map +0 -1
- package/dist/migrations.js +0 -620
- package/dist/prisma-migrations.d.ts +0 -160
- package/dist/prisma-migrations.d.ts.map +0 -1
- package/dist/prisma-migrations.js +0 -789
- package/dist/prompts.d.ts +0 -10
- package/dist/prompts.d.ts.map +0 -1
- package/dist/prompts.js +0 -41
- package/dist/pull.d.ts +0 -23
- package/dist/pull.d.ts.map +0 -1
- package/dist/pull.js +0 -424
- package/dist/schema-snapshot.d.ts +0 -45
- package/dist/schema-snapshot.d.ts.map +0 -1
- package/dist/schema-snapshot.js +0 -265
- package/dist/sql-compiler.d.ts +0 -74
- package/dist/sql-compiler.d.ts.map +0 -1
- package/dist/sql-compiler.js +0 -243
package/dist/prompts.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prompt utilities with injectable provider for tests.
|
|
3
|
-
*/
|
|
4
|
-
export interface PromptProvider {
|
|
5
|
-
question(message: string): Promise<string>;
|
|
6
|
-
}
|
|
7
|
-
export declare function createDefaultPromptProvider(): PromptProvider;
|
|
8
|
-
export declare function setPromptProvider(provider: PromptProvider | null): void;
|
|
9
|
-
export declare function getPromptProvider(): PromptProvider;
|
|
10
|
-
//# sourceMappingURL=prompts.d.ts.map
|
package/dist/prompts.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5C;AAED,wBAAgB,2BAA2B,IAAI,cAAc,CAS5D;AAID,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,GAAG,IAAI,CAEvE;AAED,wBAAgB,iBAAiB,IAAI,cAAc,CAsBlD"}
|
package/dist/prompts.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Prompt utilities with injectable provider for tests.
|
|
3
|
-
*/
|
|
4
|
-
import { createInterface } from "readline/promises";
|
|
5
|
-
import { stdin as input, stdout as output } from "process";
|
|
6
|
-
export function createDefaultPromptProvider() {
|
|
7
|
-
return {
|
|
8
|
-
async question(message) {
|
|
9
|
-
const rl = createInterface({ input, output });
|
|
10
|
-
const answer = await rl.question(message);
|
|
11
|
-
rl.close();
|
|
12
|
-
return answer;
|
|
13
|
-
},
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
let currentProvider = null;
|
|
17
|
-
export function setPromptProvider(provider) {
|
|
18
|
-
currentProvider = provider;
|
|
19
|
-
}
|
|
20
|
-
export function getPromptProvider() {
|
|
21
|
-
if (currentProvider) {
|
|
22
|
-
return currentProvider;
|
|
23
|
-
}
|
|
24
|
-
const envAnswers = process.env.ZENSTACK_KIT_PROMPT_ANSWERS;
|
|
25
|
-
if (envAnswers) {
|
|
26
|
-
try {
|
|
27
|
-
const parsed = JSON.parse(envAnswers);
|
|
28
|
-
const queue = Array.isArray(parsed) ? [...parsed] : [];
|
|
29
|
-
currentProvider = {
|
|
30
|
-
async question() {
|
|
31
|
-
return queue.shift() ?? "";
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
return currentProvider;
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
return createDefaultPromptProvider();
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return createDefaultPromptProvider();
|
|
41
|
-
}
|
package/dist/pull.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Database pull utilities
|
|
3
|
-
*
|
|
4
|
-
* Uses Kysely introspection to generate a ZenStack schema from a live database.
|
|
5
|
-
*/
|
|
6
|
-
import { type KyselyDialect } from "./kysely-adapter.js";
|
|
7
|
-
export interface PullOptions {
|
|
8
|
-
/** Database dialect */
|
|
9
|
-
dialect: KyselyDialect;
|
|
10
|
-
/** Database connection URL */
|
|
11
|
-
connectionUrl?: string;
|
|
12
|
-
/** SQLite database path (for SQLite dialect) */
|
|
13
|
-
databasePath?: string;
|
|
14
|
-
/** Output path for schema */
|
|
15
|
-
outputPath: string;
|
|
16
|
-
}
|
|
17
|
-
export interface PullResult {
|
|
18
|
-
outputPath: string;
|
|
19
|
-
schema: string;
|
|
20
|
-
tableCount: number;
|
|
21
|
-
}
|
|
22
|
-
export declare function pullSchema(options: PullOptions): Promise<PullResult>;
|
|
23
|
-
//# sourceMappingURL=pull.d.ts.map
|
package/dist/pull.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pull.d.ts","sourceRoot":"","sources":["../src/pull.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE9E,MAAM,WAAW,WAAW;IAC1B,uBAAuB;IACvB,OAAO,EAAE,aAAa,CAAC;IACvB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAmfD,wBAAsB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAwC1E"}
|
package/dist/pull.js
DELETED
|
@@ -1,424 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Database pull utilities
|
|
3
|
-
*
|
|
4
|
-
* Uses Kysely introspection to generate a ZenStack schema from a live database.
|
|
5
|
-
*/
|
|
6
|
-
import * as fs from "fs/promises";
|
|
7
|
-
import * as path from "path";
|
|
8
|
-
import { sql } from "kysely";
|
|
9
|
-
import { createKyselyAdapter } from "./kysely-adapter.js";
|
|
10
|
-
const INTERNAL_TABLES = new Set([
|
|
11
|
-
"_kysely_migration",
|
|
12
|
-
"_kysely_migration_lock",
|
|
13
|
-
"__drizzle_migrations",
|
|
14
|
-
"sqlite_sequence",
|
|
15
|
-
]);
|
|
16
|
-
function isInternalTable(name) {
|
|
17
|
-
return INTERNAL_TABLES.has(name) || name.startsWith("sqlite_");
|
|
18
|
-
}
|
|
19
|
-
function pluralize(word) {
|
|
20
|
-
if (word.endsWith("s") || word.endsWith("x") || word.endsWith("ch") || word.endsWith("sh")) {
|
|
21
|
-
return word + "es";
|
|
22
|
-
}
|
|
23
|
-
if (word.endsWith("y") && !/[aeiou]y$/i.test(word)) {
|
|
24
|
-
return word.slice(0, -1) + "ies";
|
|
25
|
-
}
|
|
26
|
-
return word + "s";
|
|
27
|
-
}
|
|
28
|
-
function singularize(word) {
|
|
29
|
-
if (word.endsWith("ies")) {
|
|
30
|
-
return word.slice(0, -3) + "y";
|
|
31
|
-
}
|
|
32
|
-
if (word.endsWith("es") && (word.endsWith("ses") || word.endsWith("xes") || word.endsWith("ches") || word.endsWith("shes"))) {
|
|
33
|
-
return word.slice(0, -2);
|
|
34
|
-
}
|
|
35
|
-
if (word.endsWith("s") && !word.endsWith("ss")) {
|
|
36
|
-
return word.slice(0, -1);
|
|
37
|
-
}
|
|
38
|
-
return word;
|
|
39
|
-
}
|
|
40
|
-
function toPascalCase(value) {
|
|
41
|
-
const parts = value.split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
42
|
-
return parts
|
|
43
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
44
|
-
.join("");
|
|
45
|
-
}
|
|
46
|
-
function toCamelCase(value) {
|
|
47
|
-
const pascal = toPascalCase(value);
|
|
48
|
-
return pascal.length > 0 ? pascal.charAt(0).toLowerCase() + pascal.slice(1) : value;
|
|
49
|
-
}
|
|
50
|
-
function normalizeType(dataType) {
|
|
51
|
-
const lower = dataType.toLowerCase();
|
|
52
|
-
const isArray = lower.endsWith("[]");
|
|
53
|
-
const base = isArray ? lower.slice(0, -2) : lower;
|
|
54
|
-
const normalized = base.replace(/\(.+\)/, "").trim();
|
|
55
|
-
if (normalized.includes("bigint"))
|
|
56
|
-
return { type: "BigInt", isArray };
|
|
57
|
-
if (normalized.includes("int"))
|
|
58
|
-
return { type: "Int", isArray };
|
|
59
|
-
if (normalized.includes("bool"))
|
|
60
|
-
return { type: "Boolean", isArray };
|
|
61
|
-
if (normalized.includes("date") || normalized.includes("time"))
|
|
62
|
-
return { type: "DateTime", isArray };
|
|
63
|
-
if (normalized.includes("json"))
|
|
64
|
-
return { type: "Json", isArray };
|
|
65
|
-
if (normalized.includes("blob") || normalized.includes("bytea") || normalized.includes("binary"))
|
|
66
|
-
return { type: "Bytes", isArray };
|
|
67
|
-
if (normalized.includes("decimal") || normalized.includes("numeric"))
|
|
68
|
-
return { type: "Decimal", isArray };
|
|
69
|
-
if (normalized.includes("real") || normalized.includes("double") || normalized.includes("float"))
|
|
70
|
-
return { type: "Float", isArray };
|
|
71
|
-
if (normalized.includes("char") || normalized.includes("text") || normalized.includes("uuid"))
|
|
72
|
-
return { type: "String", isArray };
|
|
73
|
-
return { type: "String", isArray };
|
|
74
|
-
}
|
|
75
|
-
function buildDatasourceBlock(dialect) {
|
|
76
|
-
return [
|
|
77
|
-
"datasource db {",
|
|
78
|
-
` provider = \"${dialect}\"`,
|
|
79
|
-
" url = env(\"DATABASE_URL\")",
|
|
80
|
-
"}",
|
|
81
|
-
"",
|
|
82
|
-
"generator client {",
|
|
83
|
-
" provider = \"prisma-client-js\"",
|
|
84
|
-
"}",
|
|
85
|
-
"",
|
|
86
|
-
].join("\n");
|
|
87
|
-
}
|
|
88
|
-
function buildModelBlock(options) {
|
|
89
|
-
const { table, foreignKeys, indexes, primaryKeys, allTables } = options;
|
|
90
|
-
const modelName = toPascalCase(table.name) || "Model";
|
|
91
|
-
const fieldLines = [];
|
|
92
|
-
// Get primary key columns for this table
|
|
93
|
-
const tablePk = primaryKeys.find((pk) => pk.table === table.name);
|
|
94
|
-
const pkColumns = new Set(tablePk?.columns ?? []);
|
|
95
|
-
const isCompositePk = pkColumns.size > 1;
|
|
96
|
-
// Group foreign keys by fromColumn for this table
|
|
97
|
-
const fkByColumn = new Map();
|
|
98
|
-
const incomingFks = [];
|
|
99
|
-
for (const fk of foreignKeys) {
|
|
100
|
-
if (fk.fromTable === table.name) {
|
|
101
|
-
fkByColumn.set(fk.fromColumn, fk);
|
|
102
|
-
}
|
|
103
|
-
if (fk.toTable === table.name) {
|
|
104
|
-
incomingFks.push(fk);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
// Get unique columns from indexes (excluding PK columns)
|
|
108
|
-
const uniqueColumns = new Set();
|
|
109
|
-
const compositeUniques = [];
|
|
110
|
-
for (const idx of indexes) {
|
|
111
|
-
if (idx.table === table.name && idx.isUnique) {
|
|
112
|
-
// Skip if this index matches the primary key
|
|
113
|
-
const isPkIndex = idx.columns.length === pkColumns.size &&
|
|
114
|
-
idx.columns.every((c) => pkColumns.has(c));
|
|
115
|
-
if (isPkIndex)
|
|
116
|
-
continue;
|
|
117
|
-
if (idx.columns.length === 1) {
|
|
118
|
-
uniqueColumns.add(idx.columns[0]);
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
compositeUniques.push(idx.columns);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
const sortedColumns = [...table.columns].sort((a, b) => a.name.localeCompare(b.name));
|
|
126
|
-
for (const column of sortedColumns) {
|
|
127
|
-
const fieldName = toCamelCase(column.name) || column.name;
|
|
128
|
-
const mapped = fieldName !== column.name;
|
|
129
|
-
const { type, isArray } = normalizeType(column.dataType);
|
|
130
|
-
const optional = column.isNullable ? "?" : "";
|
|
131
|
-
const modifiers = [];
|
|
132
|
-
const isPkColumn = pkColumns.has(column.name);
|
|
133
|
-
// For single-column PK, add @id to the field
|
|
134
|
-
if (isPkColumn && !isCompositePk) {
|
|
135
|
-
modifiers.push("@id");
|
|
136
|
-
if (column.isAutoIncrementing) {
|
|
137
|
-
modifiers.push("@default(autoincrement())");
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
// Add @unique for unique columns (but not PK columns)
|
|
141
|
-
if (uniqueColumns.has(column.name) && !isPkColumn) {
|
|
142
|
-
modifiers.push("@unique");
|
|
143
|
-
}
|
|
144
|
-
if (mapped) {
|
|
145
|
-
modifiers.push(`@map("${column.name}")`);
|
|
146
|
-
}
|
|
147
|
-
if (column.hasDefaultValue && !modifiers.some((m) => m.includes("@default"))) {
|
|
148
|
-
modifiers.push("@default(dbgenerated())");
|
|
149
|
-
}
|
|
150
|
-
const typeSuffix = isArray ? "[]" : "";
|
|
151
|
-
const modifierText = modifiers.length > 0 ? ` ${modifiers.join(" ")}` : "";
|
|
152
|
-
fieldLines.push(` ${fieldName} ${type}${typeSuffix}${optional}${modifierText}`);
|
|
153
|
-
}
|
|
154
|
-
// Add relation fields for outgoing foreign keys
|
|
155
|
-
for (const fk of fkByColumn.values()) {
|
|
156
|
-
if (!allTables.has(fk.toTable))
|
|
157
|
-
continue;
|
|
158
|
-
const relatedModel = toPascalCase(fk.toTable);
|
|
159
|
-
const relationFieldName = toCamelCase(singularize(fk.toTable));
|
|
160
|
-
const fkFieldName = toCamelCase(fk.fromColumn);
|
|
161
|
-
// Find if the FK column is nullable
|
|
162
|
-
const fkColumn = table.columns.find((c) => c.name === fk.fromColumn);
|
|
163
|
-
const optional = fkColumn?.isNullable ? "?" : "";
|
|
164
|
-
fieldLines.push(` ${relationFieldName} ${relatedModel}${optional} @relation(fields: [${fkFieldName}], references: [${toCamelCase(fk.toColumn)}])`);
|
|
165
|
-
}
|
|
166
|
-
// Add reverse relation fields for incoming foreign keys
|
|
167
|
-
const incomingByTable = new Map();
|
|
168
|
-
for (const fk of incomingFks) {
|
|
169
|
-
if (!allTables.has(fk.fromTable))
|
|
170
|
-
continue;
|
|
171
|
-
const existing = incomingByTable.get(fk.fromTable) ?? [];
|
|
172
|
-
existing.push(fk);
|
|
173
|
-
incomingByTable.set(fk.fromTable, existing);
|
|
174
|
-
}
|
|
175
|
-
for (const [fromTable, fks] of incomingByTable) {
|
|
176
|
-
const relatedModel = toPascalCase(fromTable);
|
|
177
|
-
const relationFieldName = toCamelCase(pluralize(fromTable));
|
|
178
|
-
// If there are multiple FKs from the same table, we need to name them
|
|
179
|
-
if (fks.length > 1) {
|
|
180
|
-
for (const fk of fks) {
|
|
181
|
-
const suffix = toPascalCase(fk.fromColumn.replace(/Id$/, ""));
|
|
182
|
-
fieldLines.push(` ${relationFieldName}By${suffix} ${relatedModel}[]`);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
fieldLines.push(` ${relationFieldName} ${relatedModel}[]`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
const lines = [`model ${modelName} {`, ...fieldLines];
|
|
190
|
-
// Add composite primary key
|
|
191
|
-
if (isCompositePk && tablePk) {
|
|
192
|
-
const fieldNames = tablePk.columns.map((c) => toCamelCase(c));
|
|
193
|
-
lines.push(` @@id([${fieldNames.join(", ")}])`);
|
|
194
|
-
}
|
|
195
|
-
// Add composite unique constraints
|
|
196
|
-
for (const columns of compositeUniques) {
|
|
197
|
-
const fieldNames = columns.map((c) => toCamelCase(c));
|
|
198
|
-
lines.push(` @@unique([${fieldNames.join(", ")}])`);
|
|
199
|
-
}
|
|
200
|
-
if (table.name !== modelName.toLowerCase()) {
|
|
201
|
-
lines.push(` @@map("${table.name}")`);
|
|
202
|
-
}
|
|
203
|
-
lines.push("}");
|
|
204
|
-
return lines.join("\n");
|
|
205
|
-
}
|
|
206
|
-
async function extractForeignKeys(db, dialect) {
|
|
207
|
-
const foreignKeys = [];
|
|
208
|
-
if (dialect === "sqlite") {
|
|
209
|
-
// SQLite: query each table's foreign keys via PRAGMA
|
|
210
|
-
const tables = await db.introspection.getTables({ withInternalKyselyTables: false });
|
|
211
|
-
for (const table of tables) {
|
|
212
|
-
if (table.isView || isInternalTable(table.name))
|
|
213
|
-
continue;
|
|
214
|
-
const result = await sql `PRAGMA foreign_key_list(${sql.raw(`"${table.name}"`)})`.execute(db);
|
|
215
|
-
for (const row of result.rows) {
|
|
216
|
-
foreignKeys.push({
|
|
217
|
-
constraintName: `fk_${table.name}_${row.from}`,
|
|
218
|
-
fromTable: table.name,
|
|
219
|
-
fromColumn: row.from,
|
|
220
|
-
toTable: row.table,
|
|
221
|
-
toColumn: row.to,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
else if (dialect === "postgres") {
|
|
227
|
-
const result = await sql `
|
|
228
|
-
SELECT
|
|
229
|
-
tc.constraint_name,
|
|
230
|
-
tc.table_name as from_table,
|
|
231
|
-
kcu.column_name as from_column,
|
|
232
|
-
ccu.table_name as to_table,
|
|
233
|
-
ccu.column_name as to_column
|
|
234
|
-
FROM information_schema.table_constraints tc
|
|
235
|
-
JOIN information_schema.key_column_usage kcu
|
|
236
|
-
ON tc.constraint_name = kcu.constraint_name
|
|
237
|
-
AND tc.table_schema = kcu.table_schema
|
|
238
|
-
JOIN information_schema.constraint_column_usage ccu
|
|
239
|
-
ON ccu.constraint_name = tc.constraint_name
|
|
240
|
-
AND ccu.table_schema = tc.table_schema
|
|
241
|
-
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
242
|
-
AND tc.table_schema = 'public'
|
|
243
|
-
`.execute(db);
|
|
244
|
-
for (const row of result.rows) {
|
|
245
|
-
foreignKeys.push({
|
|
246
|
-
constraintName: row.constraint_name,
|
|
247
|
-
fromTable: row.from_table,
|
|
248
|
-
fromColumn: row.from_column,
|
|
249
|
-
toTable: row.to_table,
|
|
250
|
-
toColumn: row.to_column,
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
else if (dialect === "mysql") {
|
|
255
|
-
const result = await sql `
|
|
256
|
-
SELECT
|
|
257
|
-
CONSTRAINT_NAME,
|
|
258
|
-
TABLE_NAME,
|
|
259
|
-
COLUMN_NAME,
|
|
260
|
-
REFERENCED_TABLE_NAME,
|
|
261
|
-
REFERENCED_COLUMN_NAME
|
|
262
|
-
FROM information_schema.KEY_COLUMN_USAGE
|
|
263
|
-
WHERE REFERENCED_TABLE_NAME IS NOT NULL
|
|
264
|
-
AND TABLE_SCHEMA = DATABASE()
|
|
265
|
-
`.execute(db);
|
|
266
|
-
for (const row of result.rows) {
|
|
267
|
-
foreignKeys.push({
|
|
268
|
-
constraintName: row.CONSTRAINT_NAME,
|
|
269
|
-
fromTable: row.TABLE_NAME,
|
|
270
|
-
fromColumn: row.COLUMN_NAME,
|
|
271
|
-
toTable: row.REFERENCED_TABLE_NAME,
|
|
272
|
-
toColumn: row.REFERENCED_COLUMN_NAME,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return foreignKeys;
|
|
277
|
-
}
|
|
278
|
-
async function extractIndexes(db, dialect, tableNames) {
|
|
279
|
-
const indexes = [];
|
|
280
|
-
if (dialect === "sqlite") {
|
|
281
|
-
for (const tableName of tableNames) {
|
|
282
|
-
const indexList = await sql `PRAGMA index_list(${sql.raw(`"${tableName}"`)})`.execute(db);
|
|
283
|
-
for (const idx of indexList.rows) {
|
|
284
|
-
// Skip internal sqlite indexes, but allow sqlite_autoindex_ which are auto-created for UNIQUE constraints
|
|
285
|
-
if (idx.name.startsWith("sqlite_") && !idx.name.startsWith("sqlite_autoindex_"))
|
|
286
|
-
continue;
|
|
287
|
-
const indexInfo = await sql `PRAGMA index_info(${sql.raw(`"${idx.name}"`)})`.execute(db);
|
|
288
|
-
indexes.push({
|
|
289
|
-
name: idx.name,
|
|
290
|
-
table: tableName,
|
|
291
|
-
columns: indexInfo.rows.map((r) => r.name),
|
|
292
|
-
isUnique: idx.unique === 1,
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
else if (dialect === "postgres") {
|
|
298
|
-
const result = await sql `
|
|
299
|
-
SELECT indexname, tablename, indexdef
|
|
300
|
-
FROM pg_indexes
|
|
301
|
-
WHERE schemaname = 'public'
|
|
302
|
-
`.execute(db);
|
|
303
|
-
for (const row of result.rows) {
|
|
304
|
-
const isUnique = row.indexdef.toUpperCase().includes("UNIQUE");
|
|
305
|
-
const colMatch = row.indexdef.match(/\(([^)]+)\)/);
|
|
306
|
-
const columns = colMatch
|
|
307
|
-
? colMatch[1].split(",").map((c) => c.trim().replace(/"/g, ""))
|
|
308
|
-
: [];
|
|
309
|
-
indexes.push({
|
|
310
|
-
name: row.indexname,
|
|
311
|
-
table: row.tablename,
|
|
312
|
-
columns,
|
|
313
|
-
isUnique,
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
else if (dialect === "mysql") {
|
|
318
|
-
for (const tableName of tableNames) {
|
|
319
|
-
const result = await sql `SHOW INDEX FROM ${sql.raw(`\`${tableName}\``)}`.execute(db);
|
|
320
|
-
const byName = new Map();
|
|
321
|
-
for (const row of result.rows) {
|
|
322
|
-
if (!byName.has(row.Key_name)) {
|
|
323
|
-
byName.set(row.Key_name, { columns: [], isUnique: row.Non_unique === 0 });
|
|
324
|
-
}
|
|
325
|
-
byName.get(row.Key_name).columns.push(row.Column_name);
|
|
326
|
-
}
|
|
327
|
-
for (const [name, info] of byName) {
|
|
328
|
-
indexes.push({
|
|
329
|
-
name,
|
|
330
|
-
table: tableName,
|
|
331
|
-
columns: info.columns,
|
|
332
|
-
isUnique: info.isUnique,
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return indexes;
|
|
338
|
-
}
|
|
339
|
-
async function extractPrimaryKeys(db, dialect, tableNames) {
|
|
340
|
-
const primaryKeys = [];
|
|
341
|
-
if (dialect === "sqlite") {
|
|
342
|
-
for (const tableName of tableNames) {
|
|
343
|
-
const tableInfo = await sql `PRAGMA table_info(${sql.raw(`"${tableName}"`)})`.execute(db);
|
|
344
|
-
const pkColumns = tableInfo.rows
|
|
345
|
-
.filter((row) => row.pk > 0)
|
|
346
|
-
.sort((a, b) => a.pk - b.pk)
|
|
347
|
-
.map((row) => row.name);
|
|
348
|
-
if (pkColumns.length > 0) {
|
|
349
|
-
primaryKeys.push({ table: tableName, columns: pkColumns });
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
else if (dialect === "postgres") {
|
|
354
|
-
const result = await sql `
|
|
355
|
-
SELECT
|
|
356
|
-
tc.table_name,
|
|
357
|
-
kcu.column_name,
|
|
358
|
-
kcu.ordinal_position
|
|
359
|
-
FROM information_schema.table_constraints tc
|
|
360
|
-
JOIN information_schema.key_column_usage kcu
|
|
361
|
-
ON tc.constraint_name = kcu.constraint_name
|
|
362
|
-
AND tc.table_schema = kcu.table_schema
|
|
363
|
-
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
364
|
-
AND tc.table_schema = 'public'
|
|
365
|
-
ORDER BY tc.table_name, kcu.ordinal_position
|
|
366
|
-
`.execute(db);
|
|
367
|
-
const byTable = new Map();
|
|
368
|
-
for (const row of result.rows) {
|
|
369
|
-
if (!byTable.has(row.table_name)) {
|
|
370
|
-
byTable.set(row.table_name, []);
|
|
371
|
-
}
|
|
372
|
-
byTable.get(row.table_name).push(row.column_name);
|
|
373
|
-
}
|
|
374
|
-
for (const [table, columns] of byTable) {
|
|
375
|
-
primaryKeys.push({ table, columns });
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
else if (dialect === "mysql") {
|
|
379
|
-
for (const tableName of tableNames) {
|
|
380
|
-
const result = await sql `SHOW INDEX FROM ${sql.raw(`\`${tableName}\``)} WHERE Key_name = 'PRIMARY'`.execute(db);
|
|
381
|
-
const columns = result.rows
|
|
382
|
-
.sort((a, b) => a.Seq_in_index - b.Seq_in_index)
|
|
383
|
-
.map((row) => row.Column_name);
|
|
384
|
-
if (columns.length > 0) {
|
|
385
|
-
primaryKeys.push({ table: tableName, columns });
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
return primaryKeys;
|
|
390
|
-
}
|
|
391
|
-
export async function pullSchema(options) {
|
|
392
|
-
const { db, destroy } = await createKyselyAdapter({
|
|
393
|
-
dialect: options.dialect,
|
|
394
|
-
connectionUrl: options.connectionUrl,
|
|
395
|
-
databasePath: options.databasePath,
|
|
396
|
-
});
|
|
397
|
-
try {
|
|
398
|
-
const tables = await db.introspection.getTables({ withInternalKyselyTables: false });
|
|
399
|
-
const filtered = tables.filter((table) => !table.isView && !isInternalTable(table.name));
|
|
400
|
-
const tableNames = filtered.map((t) => t.name);
|
|
401
|
-
const allTables = new Set(tableNames);
|
|
402
|
-
const foreignKeys = await extractForeignKeys(db, options.dialect);
|
|
403
|
-
const indexes = await extractIndexes(db, options.dialect, tableNames);
|
|
404
|
-
const primaryKeys = await extractPrimaryKeys(db, options.dialect, tableNames);
|
|
405
|
-
const blocks = filtered.map((table) => buildModelBlock({
|
|
406
|
-
table,
|
|
407
|
-
foreignKeys,
|
|
408
|
-
indexes,
|
|
409
|
-
primaryKeys,
|
|
410
|
-
allTables,
|
|
411
|
-
}));
|
|
412
|
-
const schema = [buildDatasourceBlock(options.dialect), ...blocks].join("\n\n");
|
|
413
|
-
await fs.mkdir(path.dirname(options.outputPath), { recursive: true });
|
|
414
|
-
await fs.writeFile(options.outputPath, schema.trimEnd() + "\n", "utf-8");
|
|
415
|
-
return {
|
|
416
|
-
outputPath: options.outputPath,
|
|
417
|
-
schema,
|
|
418
|
-
tableCount: filtered.length,
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
finally {
|
|
422
|
-
await destroy();
|
|
423
|
-
}
|
|
424
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Schema snapshot utilities for ZenStack schemas
|
|
3
|
-
*
|
|
4
|
-
* Uses ZenStack's AST to create a stable, diffable schema snapshot.
|
|
5
|
-
*/
|
|
6
|
-
export interface SchemaColumn {
|
|
7
|
-
name: string;
|
|
8
|
-
type: string;
|
|
9
|
-
notNull: boolean;
|
|
10
|
-
isArray: boolean;
|
|
11
|
-
default?: string | number | boolean;
|
|
12
|
-
}
|
|
13
|
-
export interface SchemaConstraint {
|
|
14
|
-
name: string;
|
|
15
|
-
columns: string[];
|
|
16
|
-
}
|
|
17
|
-
export interface SchemaIndex {
|
|
18
|
-
name: string;
|
|
19
|
-
columns: string[];
|
|
20
|
-
}
|
|
21
|
-
export interface SchemaForeignKey {
|
|
22
|
-
name: string;
|
|
23
|
-
columns: string[];
|
|
24
|
-
referencedTable: string;
|
|
25
|
-
referencedColumns: string[];
|
|
26
|
-
}
|
|
27
|
-
export interface SchemaTable {
|
|
28
|
-
name: string;
|
|
29
|
-
columns: SchemaColumn[];
|
|
30
|
-
primaryKey?: SchemaConstraint;
|
|
31
|
-
uniqueConstraints: SchemaConstraint[];
|
|
32
|
-
indexes: SchemaIndex[];
|
|
33
|
-
foreignKeys: SchemaForeignKey[];
|
|
34
|
-
}
|
|
35
|
-
export interface SchemaSnapshot {
|
|
36
|
-
tables: SchemaTable[];
|
|
37
|
-
}
|
|
38
|
-
export interface SchemaSnapshotFile {
|
|
39
|
-
version: 2;
|
|
40
|
-
createdAt: string;
|
|
41
|
-
schema: SchemaSnapshot;
|
|
42
|
-
}
|
|
43
|
-
export declare function generateSchemaSnapshot(schemaPath: string): Promise<SchemaSnapshot>;
|
|
44
|
-
export declare function createSnapshot(schema: SchemaSnapshot): SchemaSnapshotFile;
|
|
45
|
-
//# sourceMappingURL=schema-snapshot.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"schema-snapshot.d.ts","sourceRoot":"","sources":["../src/schema-snapshot.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CACrC;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,iBAAiB,EAAE,gBAAgB,EAAE,CAAC;IACtC,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,WAAW,EAAE,gBAAgB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;CACxB;AAmTD,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAaxF;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,kBAAkB,CAMzE"}
|