migratex 1.0.0 → 1.2.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 +16 -11
- package/package.json +3 -2
- package/sidecar/dist/bin/migratex-export.js +514 -0
- package/sidecar/dist/src/index.js +428 -0
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Migratex
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A schema diff and migration engine that sits on top of your ORM. it treats database changes like a graph not a numbered list enabling deterministic migrations, drift detection, and safe CI/CD workflows.
|
|
4
4
|
|
|
5
|
-
works with **drizzle**, **prisma**, and **typeorm**. supports **postgres**
|
|
5
|
+
works with **drizzle**, **prisma**, and **typeorm**. supports **postgres** and **mysql**.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Why
|
|
8
8
|
|
|
9
9
|
every ORM handles migrations the same way: numbered files in a folder. `001_create_users.sql`, `002_add_posts.sql`, and so on. works fine solo. put a team on it and two devs on different branches will both create `004_*.sql`, someone has to manually rename theirs, and you pray the SQL doesn't conflict. at scale this is a constant source of broken deploys, merge pain, and wasted time.
|
|
10
10
|
|
|
@@ -56,16 +56,20 @@ npm install migratex
|
|
|
56
56
|
```bash
|
|
57
57
|
$ migratex init
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
Initializing migratex...
|
|
60
|
+
|
|
61
|
+
Detected ORM: drizzle # or prisma, typeorm — auto-detected from package.json
|
|
62
|
+
Detected database: pg (from drizzle config)
|
|
63
|
+
Schema path [./db/schema]:
|
|
64
64
|
|
|
65
65
|
Created migratex.config.yaml
|
|
66
66
|
Created migrations/
|
|
67
|
+
|
|
68
|
+
migratex will read the database connection from your drizzle config at runtime.
|
|
67
69
|
```
|
|
68
70
|
|
|
71
|
+
no connection setup needed — migratex auto-detects your ORM from `package.json`, reads the dialect from your ORM config (`drizzle.config.ts`, `schema.prisma`, `ormconfig.json`), and uses the same env vars your ORM already uses for the database connection.
|
|
72
|
+
|
|
69
73
|
### generate
|
|
70
74
|
|
|
71
75
|
change your ORM schema, then:
|
|
@@ -178,11 +182,12 @@ each migration node has:
|
|
|
178
182
|
# migratex.config.yaml
|
|
179
183
|
orm: drizzle # drizzle | prisma | typeorm
|
|
180
184
|
dialect: pg # pg | mysql
|
|
181
|
-
|
|
182
|
-
schemaPath: ./src/schema.ts
|
|
185
|
+
schemaPath: ./db/schema
|
|
183
186
|
migrationsDir: ./migrations
|
|
184
187
|
```
|
|
185
188
|
|
|
189
|
+
no connection field needed — migratex reads it from your ORM's config at runtime. it checks `drizzle.config.ts`, `schema.prisma`, or `ormconfig.json` and uses the same env vars your ORM already uses (`DATABASE_URL`, `PGHOST`, etc.).
|
|
190
|
+
|
|
186
191
|
## license
|
|
187
192
|
|
|
188
193
|
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "migratex",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Schema diff and migration engine for ORMs (Drizzle, Prisma, TypeORM)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"bin/migratex.js",
|
|
18
|
-
"install.js"
|
|
18
|
+
"install.js",
|
|
19
|
+
"sidecar/dist/"
|
|
19
20
|
],
|
|
20
21
|
"keywords": [
|
|
21
22
|
"migrations",
|
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/drizzle.ts
|
|
13
|
+
var drizzle_exports = {};
|
|
14
|
+
__export(drizzle_exports, {
|
|
15
|
+
exportDrizzleSchema: () => exportDrizzleSchema
|
|
16
|
+
});
|
|
17
|
+
async function exportDrizzleSchema(schemaPath, dialect) {
|
|
18
|
+
const schemaModule = await import(schemaPath);
|
|
19
|
+
const tables = [];
|
|
20
|
+
const enumSet = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const [, value] of Object.entries(schemaModule)) {
|
|
22
|
+
if (!isDrizzleTable(value)) continue;
|
|
23
|
+
const table = extractTable(value, dialect, enumSet);
|
|
24
|
+
if (table) tables.push(table);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
dialect,
|
|
28
|
+
tables,
|
|
29
|
+
enums: Array.from(enumSet.entries()).map(([name, values]) => ({
|
|
30
|
+
name,
|
|
31
|
+
values
|
|
32
|
+
}))
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function isDrizzleTable(obj) {
|
|
36
|
+
return obj !== null && typeof obj === "object" && (DrizzleSymbols.IsDrizzleTable in obj || DrizzleSymbols.Columns in obj);
|
|
37
|
+
}
|
|
38
|
+
function extractTable(tableObj, dialect, enumSet) {
|
|
39
|
+
const tableName = tableObj[DrizzleSymbols.Name] || tableObj[DrizzleSymbols.BaseName];
|
|
40
|
+
if (!tableName) return null;
|
|
41
|
+
const drizzleColumns = tableObj[DrizzleSymbols.Columns];
|
|
42
|
+
if (!drizzleColumns) return null;
|
|
43
|
+
const columns = [];
|
|
44
|
+
const pkColumns = [];
|
|
45
|
+
const indexes = [];
|
|
46
|
+
const foreignKeys = [];
|
|
47
|
+
for (const [colName, colDef] of Object.entries(drizzleColumns)) {
|
|
48
|
+
const column = extractColumn(colName, colDef, dialect, enumSet);
|
|
49
|
+
columns.push(column);
|
|
50
|
+
if (colDef.primary || colDef.primaryKey) {
|
|
51
|
+
pkColumns.push(colName);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (typeof tableObj.getSQL === "function") {
|
|
55
|
+
}
|
|
56
|
+
const table = {
|
|
57
|
+
name: tableName,
|
|
58
|
+
columns,
|
|
59
|
+
indexes,
|
|
60
|
+
foreignKeys
|
|
61
|
+
};
|
|
62
|
+
if (pkColumns.length > 0) {
|
|
63
|
+
table.primaryKey = { columns: pkColumns };
|
|
64
|
+
}
|
|
65
|
+
return table;
|
|
66
|
+
}
|
|
67
|
+
function extractColumn(name, colDef, dialect, enumSet) {
|
|
68
|
+
const colType = mapDrizzleType(colDef, dialect, enumSet);
|
|
69
|
+
const column = {
|
|
70
|
+
name: colDef.name || name,
|
|
71
|
+
type: colType,
|
|
72
|
+
nullable: colDef.notNull !== true
|
|
73
|
+
};
|
|
74
|
+
if (colDef.hasDefault && colDef.default !== void 0) {
|
|
75
|
+
if (typeof colDef.default === "function") {
|
|
76
|
+
column.default = { kind: "expression", value: String(colDef.default) };
|
|
77
|
+
} else {
|
|
78
|
+
column.default = { kind: "value", value: String(colDef.default) };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (colDef.isUnique) {
|
|
82
|
+
column.unique = true;
|
|
83
|
+
}
|
|
84
|
+
return column;
|
|
85
|
+
}
|
|
86
|
+
function mapDrizzleType(colDef, dialect, enumSet) {
|
|
87
|
+
const dataType = (colDef.dataType || colDef.columnType || colDef.getSQLType?.() || "").toLowerCase();
|
|
88
|
+
const sqlName = (colDef.sqlName || "").toLowerCase();
|
|
89
|
+
if (dataType.includes("serial") || sqlName.includes("serial")) {
|
|
90
|
+
if (dataType.includes("bigserial") || sqlName.includes("bigserial")) {
|
|
91
|
+
return { kind: "serial", size: "bigserial" };
|
|
92
|
+
}
|
|
93
|
+
if (dataType.includes("smallserial") || sqlName.includes("smallserial")) {
|
|
94
|
+
return { kind: "serial", size: "smallserial" };
|
|
95
|
+
}
|
|
96
|
+
return { kind: "serial" };
|
|
97
|
+
}
|
|
98
|
+
if (dataType === "number" || dataType === "integer" || sqlName.includes("int")) {
|
|
99
|
+
if (sqlName.includes("bigint") || dataType.includes("bigint")) {
|
|
100
|
+
return { kind: "int", size: "bigint" };
|
|
101
|
+
}
|
|
102
|
+
if (sqlName.includes("smallint") || dataType.includes("smallint")) {
|
|
103
|
+
return { kind: "int", size: "smallint" };
|
|
104
|
+
}
|
|
105
|
+
return { kind: "int" };
|
|
106
|
+
}
|
|
107
|
+
if (dataType === "string" || sqlName.includes("text") || sqlName.includes("varchar")) {
|
|
108
|
+
const length = colDef.length || colDef.config?.length;
|
|
109
|
+
return { kind: "text", ...length ? { length } : {} };
|
|
110
|
+
}
|
|
111
|
+
if (dataType === "boolean" || sqlName.includes("bool")) {
|
|
112
|
+
return { kind: "boolean" };
|
|
113
|
+
}
|
|
114
|
+
if (sqlName.includes("timestamp") || dataType.includes("timestamp")) {
|
|
115
|
+
return {
|
|
116
|
+
kind: "timestamp",
|
|
117
|
+
withTimezone: sqlName.includes("tz") || colDef.withTimezone === true
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (sqlName === "date" || dataType === "date") {
|
|
121
|
+
return { kind: "date" };
|
|
122
|
+
}
|
|
123
|
+
if (sqlName.includes("json")) {
|
|
124
|
+
return { kind: "json", binary: sqlName === "jsonb" };
|
|
125
|
+
}
|
|
126
|
+
if (sqlName === "uuid" || dataType === "uuid") {
|
|
127
|
+
return { kind: "uuid" };
|
|
128
|
+
}
|
|
129
|
+
if (sqlName.includes("real") || sqlName.includes("float")) {
|
|
130
|
+
return { kind: "float" };
|
|
131
|
+
}
|
|
132
|
+
if (sqlName.includes("double")) {
|
|
133
|
+
return { kind: "float", size: "double" };
|
|
134
|
+
}
|
|
135
|
+
if (sqlName.includes("numeric") || sqlName.includes("decimal")) {
|
|
136
|
+
return {
|
|
137
|
+
kind: "decimal",
|
|
138
|
+
precision: colDef.precision,
|
|
139
|
+
scale: colDef.scale
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (colDef.enumValues && Array.isArray(colDef.enumValues)) {
|
|
143
|
+
const enumName = colDef.enumName || colDef.name || "unknown_enum";
|
|
144
|
+
enumSet.set(enumName, colDef.enumValues);
|
|
145
|
+
return { kind: "enum", enumName };
|
|
146
|
+
}
|
|
147
|
+
return { kind: "custom", raw: sqlName || dataType || "unknown" };
|
|
148
|
+
}
|
|
149
|
+
var DrizzleSymbols;
|
|
150
|
+
var init_drizzle = __esm({
|
|
151
|
+
"src/drizzle.ts"() {
|
|
152
|
+
"use strict";
|
|
153
|
+
DrizzleSymbols = {
|
|
154
|
+
Columns: /* @__PURE__ */ Symbol.for("drizzle:Columns"),
|
|
155
|
+
Name: /* @__PURE__ */ Symbol.for("drizzle:Name"),
|
|
156
|
+
Schema: /* @__PURE__ */ Symbol.for("drizzle:Schema"),
|
|
157
|
+
IsDrizzleTable: /* @__PURE__ */ Symbol.for("drizzle:IsDrizzleTable"),
|
|
158
|
+
BaseName: /* @__PURE__ */ Symbol.for("drizzle:BaseName")
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// src/prisma.ts
|
|
164
|
+
var prisma_exports = {};
|
|
165
|
+
__export(prisma_exports, {
|
|
166
|
+
exportPrismaSchema: () => exportPrismaSchema
|
|
167
|
+
});
|
|
168
|
+
async function exportPrismaSchema(schemaPath) {
|
|
169
|
+
let getDMMF;
|
|
170
|
+
try {
|
|
171
|
+
const internals = await import("@prisma/internals");
|
|
172
|
+
getDMMF = internals.getDMMF;
|
|
173
|
+
} catch {
|
|
174
|
+
throw new Error(
|
|
175
|
+
"Cannot find @prisma/internals. Install it: npm install @prisma/internals"
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
const { readFileSync } = await import("fs");
|
|
179
|
+
const datamodel = readFileSync(schemaPath, "utf-8");
|
|
180
|
+
const dialect = detectDialect(datamodel);
|
|
181
|
+
const dmmf = await getDMMF({ datamodel });
|
|
182
|
+
const tables = [];
|
|
183
|
+
const enums = [];
|
|
184
|
+
for (const model of dmmf.datamodel.models) {
|
|
185
|
+
tables.push(convertModel(model, dmmf.datamodel.models, dialect));
|
|
186
|
+
}
|
|
187
|
+
for (const enumDef of dmmf.datamodel.enums) {
|
|
188
|
+
enums.push({
|
|
189
|
+
name: enumDef.name,
|
|
190
|
+
values: enumDef.values.map((v) => v.name)
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return { dialect, tables, enums };
|
|
194
|
+
}
|
|
195
|
+
function detectDialect(datamodel) {
|
|
196
|
+
const match = datamodel.match(/provider\s*=\s*"(\w+)"/);
|
|
197
|
+
if (match) {
|
|
198
|
+
const provider = match[1].toLowerCase();
|
|
199
|
+
if (provider === "mysql") return "mysql";
|
|
200
|
+
}
|
|
201
|
+
return "pg";
|
|
202
|
+
}
|
|
203
|
+
function convertModel(model, allModels, dialect) {
|
|
204
|
+
const columns = [];
|
|
205
|
+
const foreignKeys = [];
|
|
206
|
+
const indexes = [];
|
|
207
|
+
const pkColumns = [];
|
|
208
|
+
for (const field of model.fields) {
|
|
209
|
+
if (field.kind === "object") continue;
|
|
210
|
+
const column = convertField(field, dialect);
|
|
211
|
+
columns.push(column);
|
|
212
|
+
if (field.isId) {
|
|
213
|
+
pkColumns.push(field.name);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for (const field of model.fields) {
|
|
217
|
+
if (field.kind === "object" && field.relationFromFields?.length > 0) {
|
|
218
|
+
const fk = {
|
|
219
|
+
name: `fk_${model.name.toLowerCase()}_${field.name}`,
|
|
220
|
+
columns: field.relationFromFields,
|
|
221
|
+
referencedTable: field.type.toLowerCase(),
|
|
222
|
+
referencedColumns: field.relationToFields || ["id"],
|
|
223
|
+
onDelete: mapPrismaAction(field.relationOnDelete),
|
|
224
|
+
onUpdate: mapPrismaAction(field.relationOnUpdate)
|
|
225
|
+
};
|
|
226
|
+
foreignKeys.push(fk);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (model.uniqueFields) {
|
|
230
|
+
for (const fields of model.uniqueFields) {
|
|
231
|
+
indexes.push({
|
|
232
|
+
name: `${model.name.toLowerCase()}_${fields.join("_")}_unique`,
|
|
233
|
+
columns: fields,
|
|
234
|
+
unique: true
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const table = {
|
|
239
|
+
// Prisma uses PascalCase model names; DB uses snake_case
|
|
240
|
+
// The @@map attribute overrides this, but dbName captures it
|
|
241
|
+
name: model.dbName || model.name.toLowerCase(),
|
|
242
|
+
columns,
|
|
243
|
+
indexes,
|
|
244
|
+
foreignKeys
|
|
245
|
+
};
|
|
246
|
+
if (pkColumns.length > 0) {
|
|
247
|
+
table.primaryKey = { columns: pkColumns };
|
|
248
|
+
}
|
|
249
|
+
return table;
|
|
250
|
+
}
|
|
251
|
+
function convertField(field, dialect) {
|
|
252
|
+
return {
|
|
253
|
+
name: field.dbName || field.name,
|
|
254
|
+
type: mapPrismaType(field, dialect),
|
|
255
|
+
nullable: !field.isRequired,
|
|
256
|
+
...field.hasDefaultValue && field.default !== void 0 ? { default: parsePrismaDefault(field.default) } : {},
|
|
257
|
+
...field.isId ? { primaryKey: true } : {},
|
|
258
|
+
...field.isUnique ? { unique: true } : {}
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function mapPrismaType(field, dialect) {
|
|
262
|
+
const nativeType = field.nativeType;
|
|
263
|
+
switch (field.type) {
|
|
264
|
+
case "Int":
|
|
265
|
+
if (nativeType) {
|
|
266
|
+
if (nativeType[0] === "SmallInt") return { kind: "int", size: "smallint" };
|
|
267
|
+
if (nativeType[0] === "BigInt") return { kind: "int", size: "bigint" };
|
|
268
|
+
}
|
|
269
|
+
return { kind: "int" };
|
|
270
|
+
case "BigInt":
|
|
271
|
+
return { kind: "int", size: "bigint" };
|
|
272
|
+
case "Float":
|
|
273
|
+
return { kind: "float", size: "double" };
|
|
274
|
+
case "Decimal":
|
|
275
|
+
return {
|
|
276
|
+
kind: "decimal",
|
|
277
|
+
precision: nativeType?.[1]?.precision || 65,
|
|
278
|
+
scale: nativeType?.[1]?.scale || 30
|
|
279
|
+
};
|
|
280
|
+
case "String":
|
|
281
|
+
if (nativeType?.[0] === "VarChar") {
|
|
282
|
+
return { kind: "text", length: nativeType[1]?.length || 255 };
|
|
283
|
+
}
|
|
284
|
+
return { kind: "text" };
|
|
285
|
+
case "Boolean":
|
|
286
|
+
return { kind: "boolean" };
|
|
287
|
+
case "DateTime":
|
|
288
|
+
return { kind: "timestamp", withTimezone: dialect === "pg" };
|
|
289
|
+
case "Json":
|
|
290
|
+
return { kind: "json", binary: dialect === "pg" };
|
|
291
|
+
case "Bytes":
|
|
292
|
+
return { kind: "bytea" };
|
|
293
|
+
default:
|
|
294
|
+
if (field.kind === "enum") {
|
|
295
|
+
return { kind: "enum", enumName: field.type };
|
|
296
|
+
}
|
|
297
|
+
return { kind: "custom", raw: field.type };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function parsePrismaDefault(def) {
|
|
301
|
+
if (typeof def === "object" && def.name) {
|
|
302
|
+
return { kind: "expression", value: `${def.name}()` };
|
|
303
|
+
}
|
|
304
|
+
return { kind: "value", value: String(def) };
|
|
305
|
+
}
|
|
306
|
+
function mapPrismaAction(action) {
|
|
307
|
+
if (!action) return void 0;
|
|
308
|
+
const map = {
|
|
309
|
+
Cascade: "CASCADE",
|
|
310
|
+
SetNull: "SET NULL",
|
|
311
|
+
SetDefault: "SET DEFAULT",
|
|
312
|
+
Restrict: "RESTRICT",
|
|
313
|
+
NoAction: "NO ACTION"
|
|
314
|
+
};
|
|
315
|
+
return map[action];
|
|
316
|
+
}
|
|
317
|
+
var init_prisma = __esm({
|
|
318
|
+
"src/prisma.ts"() {
|
|
319
|
+
"use strict";
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// src/typeorm.ts
|
|
324
|
+
var typeorm_exports = {};
|
|
325
|
+
__export(typeorm_exports, {
|
|
326
|
+
exportTypeORMSchema: () => exportTypeORMSchema
|
|
327
|
+
});
|
|
328
|
+
async function exportTypeORMSchema(schemaPath, dialect) {
|
|
329
|
+
let getMetadataArgsStorage;
|
|
330
|
+
try {
|
|
331
|
+
const typeorm = await import("typeorm");
|
|
332
|
+
getMetadataArgsStorage = typeorm.getMetadataArgsStorage;
|
|
333
|
+
} catch {
|
|
334
|
+
throw new Error("Cannot find typeorm. Install it: npm install typeorm reflect-metadata");
|
|
335
|
+
}
|
|
336
|
+
await import(schemaPath);
|
|
337
|
+
const storage = getMetadataArgsStorage();
|
|
338
|
+
const tables = [];
|
|
339
|
+
for (const tableArgs of storage.tables) {
|
|
340
|
+
const table = convertTypeORMTable(tableArgs, storage, dialect);
|
|
341
|
+
tables.push(table);
|
|
342
|
+
}
|
|
343
|
+
return { dialect, tables };
|
|
344
|
+
}
|
|
345
|
+
function convertTypeORMTable(tableArgs, storage, dialect) {
|
|
346
|
+
const entityTarget = tableArgs.target;
|
|
347
|
+
const tableName = tableArgs.name || entityTarget.name?.toLowerCase() || "unknown";
|
|
348
|
+
const columnArgs = storage.columns.filter(
|
|
349
|
+
(c) => c.target === entityTarget || c.target?.prototype instanceof entityTarget
|
|
350
|
+
);
|
|
351
|
+
const columns = [];
|
|
352
|
+
const foreignKeys = [];
|
|
353
|
+
for (const colArg of columnArgs) {
|
|
354
|
+
columns.push(convertTypeORMColumn(colArg, dialect));
|
|
355
|
+
}
|
|
356
|
+
const relations = storage.relations.filter(
|
|
357
|
+
(r) => r.target === entityTarget
|
|
358
|
+
);
|
|
359
|
+
for (const rel of relations) {
|
|
360
|
+
const joinColumns = storage.joinColumns.filter(
|
|
361
|
+
(jc) => jc.target === entityTarget && jc.propertyName === rel.propertyName
|
|
362
|
+
);
|
|
363
|
+
for (const jc of joinColumns) {
|
|
364
|
+
foreignKeys.push({
|
|
365
|
+
name: `fk_${tableName}_${jc.name || rel.propertyName}`,
|
|
366
|
+
columns: [jc.name || `${rel.propertyName}Id`],
|
|
367
|
+
referencedTable: typeof rel.type === "function" ? rel.type().name?.toLowerCase() : String(rel.type).toLowerCase(),
|
|
368
|
+
referencedColumns: [jc.referencedColumnName || "id"]
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const pkColumns = columns.filter((c) => c.primaryKey).map((c) => c.name);
|
|
373
|
+
const table = {
|
|
374
|
+
name: tableName,
|
|
375
|
+
columns,
|
|
376
|
+
foreignKeys
|
|
377
|
+
};
|
|
378
|
+
if (pkColumns.length > 0) {
|
|
379
|
+
table.primaryKey = { columns: pkColumns };
|
|
380
|
+
}
|
|
381
|
+
return table;
|
|
382
|
+
}
|
|
383
|
+
function convertTypeORMColumn(colArg, dialect) {
|
|
384
|
+
const options = colArg.options || {};
|
|
385
|
+
const isPrimary = colArg.mode === "regular" ? options.primary : colArg.mode === "objectId";
|
|
386
|
+
return {
|
|
387
|
+
name: options.name || colArg.propertyName,
|
|
388
|
+
type: mapTypeORMType(colArg, options, dialect),
|
|
389
|
+
nullable: options.nullable === true,
|
|
390
|
+
...isPrimary ? { primaryKey: true } : {},
|
|
391
|
+
...options.unique ? { unique: true } : {},
|
|
392
|
+
...options.default !== void 0 ? {
|
|
393
|
+
default: {
|
|
394
|
+
kind: typeof options.default === "string" && options.default.includes("(") ? "expression" : "value",
|
|
395
|
+
value: String(options.default)
|
|
396
|
+
}
|
|
397
|
+
} : {}
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
function mapTypeORMType(colArg, options, dialect) {
|
|
401
|
+
const type = options.type || colArg.mode === "createDate" || colArg.mode === "updateDate" ? "timestamp" : "unknown";
|
|
402
|
+
switch (type.toLowerCase()) {
|
|
403
|
+
case "int":
|
|
404
|
+
case "integer":
|
|
405
|
+
case "int4":
|
|
406
|
+
return { kind: "int" };
|
|
407
|
+
case "smallint":
|
|
408
|
+
case "int2":
|
|
409
|
+
return { kind: "int", size: "smallint" };
|
|
410
|
+
case "bigint":
|
|
411
|
+
case "int8":
|
|
412
|
+
return { kind: "int", size: "bigint" };
|
|
413
|
+
case "varchar":
|
|
414
|
+
case "character varying":
|
|
415
|
+
return { kind: "text", length: options.length || 255 };
|
|
416
|
+
case "text":
|
|
417
|
+
return { kind: "text" };
|
|
418
|
+
case "boolean":
|
|
419
|
+
case "bool":
|
|
420
|
+
return { kind: "boolean" };
|
|
421
|
+
case "timestamp":
|
|
422
|
+
case "timestamp with time zone":
|
|
423
|
+
case "timestamptz":
|
|
424
|
+
return { kind: "timestamp", withTimezone: dialect === "pg" };
|
|
425
|
+
case "timestamp without time zone":
|
|
426
|
+
return { kind: "timestamp" };
|
|
427
|
+
case "date":
|
|
428
|
+
return { kind: "date" };
|
|
429
|
+
case "json":
|
|
430
|
+
return { kind: "json" };
|
|
431
|
+
case "jsonb":
|
|
432
|
+
return { kind: "json", binary: true };
|
|
433
|
+
case "uuid":
|
|
434
|
+
return { kind: "uuid" };
|
|
435
|
+
case "float":
|
|
436
|
+
case "real":
|
|
437
|
+
case "float4":
|
|
438
|
+
return { kind: "float" };
|
|
439
|
+
case "double precision":
|
|
440
|
+
case "float8":
|
|
441
|
+
return { kind: "float", size: "double" };
|
|
442
|
+
case "decimal":
|
|
443
|
+
case "numeric":
|
|
444
|
+
return {
|
|
445
|
+
kind: "decimal",
|
|
446
|
+
precision: options.precision,
|
|
447
|
+
scale: options.scale
|
|
448
|
+
};
|
|
449
|
+
case "bytea":
|
|
450
|
+
case "blob":
|
|
451
|
+
return { kind: "bytea" };
|
|
452
|
+
case "enum":
|
|
453
|
+
return { kind: "enum", enumName: options.enum?.name || "unknown" };
|
|
454
|
+
default:
|
|
455
|
+
return { kind: "custom", raw: type };
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
var init_typeorm = __esm({
|
|
459
|
+
"src/typeorm.ts"() {
|
|
460
|
+
"use strict";
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// bin/migratex-export.ts
|
|
465
|
+
import { resolve } from "path";
|
|
466
|
+
async function main() {
|
|
467
|
+
const args = parseArgs(process.argv.slice(2));
|
|
468
|
+
if (!args.orm) {
|
|
469
|
+
console.error("Usage: migratex-export --orm <drizzle|prisma|typeorm> --schema <path> [--dialect <pg|mysql>]");
|
|
470
|
+
process.exit(1);
|
|
471
|
+
}
|
|
472
|
+
if (!args.schema) {
|
|
473
|
+
console.error("Error: --schema is required");
|
|
474
|
+
process.exit(1);
|
|
475
|
+
}
|
|
476
|
+
const schemaPath = resolve(process.cwd(), args.schema);
|
|
477
|
+
const dialect = args.dialect || "pg";
|
|
478
|
+
let schema;
|
|
479
|
+
switch (args.orm) {
|
|
480
|
+
case "drizzle": {
|
|
481
|
+
const { exportDrizzleSchema: exportDrizzleSchema2 } = await Promise.resolve().then(() => (init_drizzle(), drizzle_exports));
|
|
482
|
+
schema = await exportDrizzleSchema2(schemaPath, dialect);
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
case "prisma": {
|
|
486
|
+
const { exportPrismaSchema: exportPrismaSchema2 } = await Promise.resolve().then(() => (init_prisma(), prisma_exports));
|
|
487
|
+
schema = await exportPrismaSchema2(schemaPath);
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
case "typeorm": {
|
|
491
|
+
const { exportTypeORMSchema: exportTypeORMSchema2 } = await Promise.resolve().then(() => (init_typeorm(), typeorm_exports));
|
|
492
|
+
schema = await exportTypeORMSchema2(schemaPath, dialect);
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
default:
|
|
496
|
+
console.error(`Unknown ORM: ${args.orm}. Supported: drizzle, prisma, typeorm`);
|
|
497
|
+
process.exit(1);
|
|
498
|
+
}
|
|
499
|
+
console.log(JSON.stringify(schema, null, 2));
|
|
500
|
+
}
|
|
501
|
+
function parseArgs(argv) {
|
|
502
|
+
const args = {};
|
|
503
|
+
for (let i = 0; i < argv.length; i++) {
|
|
504
|
+
if (argv[i].startsWith("--") && i + 1 < argv.length) {
|
|
505
|
+
args[argv[i].slice(2)] = argv[i + 1];
|
|
506
|
+
i++;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return args;
|
|
510
|
+
}
|
|
511
|
+
main().catch((err) => {
|
|
512
|
+
console.error("Error:", err.message);
|
|
513
|
+
process.exit(1);
|
|
514
|
+
});
|
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
// src/drizzle.ts
|
|
2
|
+
var DrizzleSymbols = {
|
|
3
|
+
Columns: /* @__PURE__ */ Symbol.for("drizzle:Columns"),
|
|
4
|
+
Name: /* @__PURE__ */ Symbol.for("drizzle:Name"),
|
|
5
|
+
Schema: /* @__PURE__ */ Symbol.for("drizzle:Schema"),
|
|
6
|
+
IsDrizzleTable: /* @__PURE__ */ Symbol.for("drizzle:IsDrizzleTable"),
|
|
7
|
+
BaseName: /* @__PURE__ */ Symbol.for("drizzle:BaseName")
|
|
8
|
+
};
|
|
9
|
+
async function exportDrizzleSchema(schemaPath, dialect) {
|
|
10
|
+
const schemaModule = await import(schemaPath);
|
|
11
|
+
const tables = [];
|
|
12
|
+
const enumSet = /* @__PURE__ */ new Map();
|
|
13
|
+
for (const [, value] of Object.entries(schemaModule)) {
|
|
14
|
+
if (!isDrizzleTable(value)) continue;
|
|
15
|
+
const table = extractTable(value, dialect, enumSet);
|
|
16
|
+
if (table) tables.push(table);
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
dialect,
|
|
20
|
+
tables,
|
|
21
|
+
enums: Array.from(enumSet.entries()).map(([name, values]) => ({
|
|
22
|
+
name,
|
|
23
|
+
values
|
|
24
|
+
}))
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function isDrizzleTable(obj) {
|
|
28
|
+
return obj !== null && typeof obj === "object" && (DrizzleSymbols.IsDrizzleTable in obj || DrizzleSymbols.Columns in obj);
|
|
29
|
+
}
|
|
30
|
+
function extractTable(tableObj, dialect, enumSet) {
|
|
31
|
+
const tableName = tableObj[DrizzleSymbols.Name] || tableObj[DrizzleSymbols.BaseName];
|
|
32
|
+
if (!tableName) return null;
|
|
33
|
+
const drizzleColumns = tableObj[DrizzleSymbols.Columns];
|
|
34
|
+
if (!drizzleColumns) return null;
|
|
35
|
+
const columns = [];
|
|
36
|
+
const pkColumns = [];
|
|
37
|
+
const indexes = [];
|
|
38
|
+
const foreignKeys = [];
|
|
39
|
+
for (const [colName, colDef] of Object.entries(drizzleColumns)) {
|
|
40
|
+
const column = extractColumn(colName, colDef, dialect, enumSet);
|
|
41
|
+
columns.push(column);
|
|
42
|
+
if (colDef.primary || colDef.primaryKey) {
|
|
43
|
+
pkColumns.push(colName);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (typeof tableObj.getSQL === "function") {
|
|
47
|
+
}
|
|
48
|
+
const table = {
|
|
49
|
+
name: tableName,
|
|
50
|
+
columns,
|
|
51
|
+
indexes,
|
|
52
|
+
foreignKeys
|
|
53
|
+
};
|
|
54
|
+
if (pkColumns.length > 0) {
|
|
55
|
+
table.primaryKey = { columns: pkColumns };
|
|
56
|
+
}
|
|
57
|
+
return table;
|
|
58
|
+
}
|
|
59
|
+
function extractColumn(name, colDef, dialect, enumSet) {
|
|
60
|
+
const colType = mapDrizzleType(colDef, dialect, enumSet);
|
|
61
|
+
const column = {
|
|
62
|
+
name: colDef.name || name,
|
|
63
|
+
type: colType,
|
|
64
|
+
nullable: colDef.notNull !== true
|
|
65
|
+
};
|
|
66
|
+
if (colDef.hasDefault && colDef.default !== void 0) {
|
|
67
|
+
if (typeof colDef.default === "function") {
|
|
68
|
+
column.default = { kind: "expression", value: String(colDef.default) };
|
|
69
|
+
} else {
|
|
70
|
+
column.default = { kind: "value", value: String(colDef.default) };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (colDef.isUnique) {
|
|
74
|
+
column.unique = true;
|
|
75
|
+
}
|
|
76
|
+
return column;
|
|
77
|
+
}
|
|
78
|
+
function mapDrizzleType(colDef, dialect, enumSet) {
|
|
79
|
+
const dataType = (colDef.dataType || colDef.columnType || colDef.getSQLType?.() || "").toLowerCase();
|
|
80
|
+
const sqlName = (colDef.sqlName || "").toLowerCase();
|
|
81
|
+
if (dataType.includes("serial") || sqlName.includes("serial")) {
|
|
82
|
+
if (dataType.includes("bigserial") || sqlName.includes("bigserial")) {
|
|
83
|
+
return { kind: "serial", size: "bigserial" };
|
|
84
|
+
}
|
|
85
|
+
if (dataType.includes("smallserial") || sqlName.includes("smallserial")) {
|
|
86
|
+
return { kind: "serial", size: "smallserial" };
|
|
87
|
+
}
|
|
88
|
+
return { kind: "serial" };
|
|
89
|
+
}
|
|
90
|
+
if (dataType === "number" || dataType === "integer" || sqlName.includes("int")) {
|
|
91
|
+
if (sqlName.includes("bigint") || dataType.includes("bigint")) {
|
|
92
|
+
return { kind: "int", size: "bigint" };
|
|
93
|
+
}
|
|
94
|
+
if (sqlName.includes("smallint") || dataType.includes("smallint")) {
|
|
95
|
+
return { kind: "int", size: "smallint" };
|
|
96
|
+
}
|
|
97
|
+
return { kind: "int" };
|
|
98
|
+
}
|
|
99
|
+
if (dataType === "string" || sqlName.includes("text") || sqlName.includes("varchar")) {
|
|
100
|
+
const length = colDef.length || colDef.config?.length;
|
|
101
|
+
return { kind: "text", ...length ? { length } : {} };
|
|
102
|
+
}
|
|
103
|
+
if (dataType === "boolean" || sqlName.includes("bool")) {
|
|
104
|
+
return { kind: "boolean" };
|
|
105
|
+
}
|
|
106
|
+
if (sqlName.includes("timestamp") || dataType.includes("timestamp")) {
|
|
107
|
+
return {
|
|
108
|
+
kind: "timestamp",
|
|
109
|
+
withTimezone: sqlName.includes("tz") || colDef.withTimezone === true
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (sqlName === "date" || dataType === "date") {
|
|
113
|
+
return { kind: "date" };
|
|
114
|
+
}
|
|
115
|
+
if (sqlName.includes("json")) {
|
|
116
|
+
return { kind: "json", binary: sqlName === "jsonb" };
|
|
117
|
+
}
|
|
118
|
+
if (sqlName === "uuid" || dataType === "uuid") {
|
|
119
|
+
return { kind: "uuid" };
|
|
120
|
+
}
|
|
121
|
+
if (sqlName.includes("real") || sqlName.includes("float")) {
|
|
122
|
+
return { kind: "float" };
|
|
123
|
+
}
|
|
124
|
+
if (sqlName.includes("double")) {
|
|
125
|
+
return { kind: "float", size: "double" };
|
|
126
|
+
}
|
|
127
|
+
if (sqlName.includes("numeric") || sqlName.includes("decimal")) {
|
|
128
|
+
return {
|
|
129
|
+
kind: "decimal",
|
|
130
|
+
precision: colDef.precision,
|
|
131
|
+
scale: colDef.scale
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (colDef.enumValues && Array.isArray(colDef.enumValues)) {
|
|
135
|
+
const enumName = colDef.enumName || colDef.name || "unknown_enum";
|
|
136
|
+
enumSet.set(enumName, colDef.enumValues);
|
|
137
|
+
return { kind: "enum", enumName };
|
|
138
|
+
}
|
|
139
|
+
return { kind: "custom", raw: sqlName || dataType || "unknown" };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/prisma.ts
|
|
143
|
+
async function exportPrismaSchema(schemaPath) {
|
|
144
|
+
let getDMMF;
|
|
145
|
+
try {
|
|
146
|
+
const internals = await import("@prisma/internals");
|
|
147
|
+
getDMMF = internals.getDMMF;
|
|
148
|
+
} catch {
|
|
149
|
+
throw new Error(
|
|
150
|
+
"Cannot find @prisma/internals. Install it: npm install @prisma/internals"
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const { readFileSync } = await import("fs");
|
|
154
|
+
const datamodel = readFileSync(schemaPath, "utf-8");
|
|
155
|
+
const dialect = detectDialect(datamodel);
|
|
156
|
+
const dmmf = await getDMMF({ datamodel });
|
|
157
|
+
const tables = [];
|
|
158
|
+
const enums = [];
|
|
159
|
+
for (const model of dmmf.datamodel.models) {
|
|
160
|
+
tables.push(convertModel(model, dmmf.datamodel.models, dialect));
|
|
161
|
+
}
|
|
162
|
+
for (const enumDef of dmmf.datamodel.enums) {
|
|
163
|
+
enums.push({
|
|
164
|
+
name: enumDef.name,
|
|
165
|
+
values: enumDef.values.map((v) => v.name)
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return { dialect, tables, enums };
|
|
169
|
+
}
|
|
170
|
+
function detectDialect(datamodel) {
|
|
171
|
+
const match = datamodel.match(/provider\s*=\s*"(\w+)"/);
|
|
172
|
+
if (match) {
|
|
173
|
+
const provider = match[1].toLowerCase();
|
|
174
|
+
if (provider === "mysql") return "mysql";
|
|
175
|
+
}
|
|
176
|
+
return "pg";
|
|
177
|
+
}
|
|
178
|
+
function convertModel(model, allModels, dialect) {
|
|
179
|
+
const columns = [];
|
|
180
|
+
const foreignKeys = [];
|
|
181
|
+
const indexes = [];
|
|
182
|
+
const pkColumns = [];
|
|
183
|
+
for (const field of model.fields) {
|
|
184
|
+
if (field.kind === "object") continue;
|
|
185
|
+
const column = convertField(field, dialect);
|
|
186
|
+
columns.push(column);
|
|
187
|
+
if (field.isId) {
|
|
188
|
+
pkColumns.push(field.name);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
for (const field of model.fields) {
|
|
192
|
+
if (field.kind === "object" && field.relationFromFields?.length > 0) {
|
|
193
|
+
const fk = {
|
|
194
|
+
name: `fk_${model.name.toLowerCase()}_${field.name}`,
|
|
195
|
+
columns: field.relationFromFields,
|
|
196
|
+
referencedTable: field.type.toLowerCase(),
|
|
197
|
+
referencedColumns: field.relationToFields || ["id"],
|
|
198
|
+
onDelete: mapPrismaAction(field.relationOnDelete),
|
|
199
|
+
onUpdate: mapPrismaAction(field.relationOnUpdate)
|
|
200
|
+
};
|
|
201
|
+
foreignKeys.push(fk);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (model.uniqueFields) {
|
|
205
|
+
for (const fields of model.uniqueFields) {
|
|
206
|
+
indexes.push({
|
|
207
|
+
name: `${model.name.toLowerCase()}_${fields.join("_")}_unique`,
|
|
208
|
+
columns: fields,
|
|
209
|
+
unique: true
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const table = {
|
|
214
|
+
// Prisma uses PascalCase model names; DB uses snake_case
|
|
215
|
+
// The @@map attribute overrides this, but dbName captures it
|
|
216
|
+
name: model.dbName || model.name.toLowerCase(),
|
|
217
|
+
columns,
|
|
218
|
+
indexes,
|
|
219
|
+
foreignKeys
|
|
220
|
+
};
|
|
221
|
+
if (pkColumns.length > 0) {
|
|
222
|
+
table.primaryKey = { columns: pkColumns };
|
|
223
|
+
}
|
|
224
|
+
return table;
|
|
225
|
+
}
|
|
226
|
+
function convertField(field, dialect) {
|
|
227
|
+
return {
|
|
228
|
+
name: field.dbName || field.name,
|
|
229
|
+
type: mapPrismaType(field, dialect),
|
|
230
|
+
nullable: !field.isRequired,
|
|
231
|
+
...field.hasDefaultValue && field.default !== void 0 ? { default: parsePrismaDefault(field.default) } : {},
|
|
232
|
+
...field.isId ? { primaryKey: true } : {},
|
|
233
|
+
...field.isUnique ? { unique: true } : {}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function mapPrismaType(field, dialect) {
|
|
237
|
+
const nativeType = field.nativeType;
|
|
238
|
+
switch (field.type) {
|
|
239
|
+
case "Int":
|
|
240
|
+
if (nativeType) {
|
|
241
|
+
if (nativeType[0] === "SmallInt") return { kind: "int", size: "smallint" };
|
|
242
|
+
if (nativeType[0] === "BigInt") return { kind: "int", size: "bigint" };
|
|
243
|
+
}
|
|
244
|
+
return { kind: "int" };
|
|
245
|
+
case "BigInt":
|
|
246
|
+
return { kind: "int", size: "bigint" };
|
|
247
|
+
case "Float":
|
|
248
|
+
return { kind: "float", size: "double" };
|
|
249
|
+
case "Decimal":
|
|
250
|
+
return {
|
|
251
|
+
kind: "decimal",
|
|
252
|
+
precision: nativeType?.[1]?.precision || 65,
|
|
253
|
+
scale: nativeType?.[1]?.scale || 30
|
|
254
|
+
};
|
|
255
|
+
case "String":
|
|
256
|
+
if (nativeType?.[0] === "VarChar") {
|
|
257
|
+
return { kind: "text", length: nativeType[1]?.length || 255 };
|
|
258
|
+
}
|
|
259
|
+
return { kind: "text" };
|
|
260
|
+
case "Boolean":
|
|
261
|
+
return { kind: "boolean" };
|
|
262
|
+
case "DateTime":
|
|
263
|
+
return { kind: "timestamp", withTimezone: dialect === "pg" };
|
|
264
|
+
case "Json":
|
|
265
|
+
return { kind: "json", binary: dialect === "pg" };
|
|
266
|
+
case "Bytes":
|
|
267
|
+
return { kind: "bytea" };
|
|
268
|
+
default:
|
|
269
|
+
if (field.kind === "enum") {
|
|
270
|
+
return { kind: "enum", enumName: field.type };
|
|
271
|
+
}
|
|
272
|
+
return { kind: "custom", raw: field.type };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function parsePrismaDefault(def) {
|
|
276
|
+
if (typeof def === "object" && def.name) {
|
|
277
|
+
return { kind: "expression", value: `${def.name}()` };
|
|
278
|
+
}
|
|
279
|
+
return { kind: "value", value: String(def) };
|
|
280
|
+
}
|
|
281
|
+
function mapPrismaAction(action) {
|
|
282
|
+
if (!action) return void 0;
|
|
283
|
+
const map = {
|
|
284
|
+
Cascade: "CASCADE",
|
|
285
|
+
SetNull: "SET NULL",
|
|
286
|
+
SetDefault: "SET DEFAULT",
|
|
287
|
+
Restrict: "RESTRICT",
|
|
288
|
+
NoAction: "NO ACTION"
|
|
289
|
+
};
|
|
290
|
+
return map[action];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/typeorm.ts
|
|
294
|
+
async function exportTypeORMSchema(schemaPath, dialect) {
|
|
295
|
+
let getMetadataArgsStorage;
|
|
296
|
+
try {
|
|
297
|
+
const typeorm = await import("typeorm");
|
|
298
|
+
getMetadataArgsStorage = typeorm.getMetadataArgsStorage;
|
|
299
|
+
} catch {
|
|
300
|
+
throw new Error("Cannot find typeorm. Install it: npm install typeorm reflect-metadata");
|
|
301
|
+
}
|
|
302
|
+
await import(schemaPath);
|
|
303
|
+
const storage = getMetadataArgsStorage();
|
|
304
|
+
const tables = [];
|
|
305
|
+
for (const tableArgs of storage.tables) {
|
|
306
|
+
const table = convertTypeORMTable(tableArgs, storage, dialect);
|
|
307
|
+
tables.push(table);
|
|
308
|
+
}
|
|
309
|
+
return { dialect, tables };
|
|
310
|
+
}
|
|
311
|
+
function convertTypeORMTable(tableArgs, storage, dialect) {
|
|
312
|
+
const entityTarget = tableArgs.target;
|
|
313
|
+
const tableName = tableArgs.name || entityTarget.name?.toLowerCase() || "unknown";
|
|
314
|
+
const columnArgs = storage.columns.filter(
|
|
315
|
+
(c) => c.target === entityTarget || c.target?.prototype instanceof entityTarget
|
|
316
|
+
);
|
|
317
|
+
const columns = [];
|
|
318
|
+
const foreignKeys = [];
|
|
319
|
+
for (const colArg of columnArgs) {
|
|
320
|
+
columns.push(convertTypeORMColumn(colArg, dialect));
|
|
321
|
+
}
|
|
322
|
+
const relations = storage.relations.filter(
|
|
323
|
+
(r) => r.target === entityTarget
|
|
324
|
+
);
|
|
325
|
+
for (const rel of relations) {
|
|
326
|
+
const joinColumns = storage.joinColumns.filter(
|
|
327
|
+
(jc) => jc.target === entityTarget && jc.propertyName === rel.propertyName
|
|
328
|
+
);
|
|
329
|
+
for (const jc of joinColumns) {
|
|
330
|
+
foreignKeys.push({
|
|
331
|
+
name: `fk_${tableName}_${jc.name || rel.propertyName}`,
|
|
332
|
+
columns: [jc.name || `${rel.propertyName}Id`],
|
|
333
|
+
referencedTable: typeof rel.type === "function" ? rel.type().name?.toLowerCase() : String(rel.type).toLowerCase(),
|
|
334
|
+
referencedColumns: [jc.referencedColumnName || "id"]
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const pkColumns = columns.filter((c) => c.primaryKey).map((c) => c.name);
|
|
339
|
+
const table = {
|
|
340
|
+
name: tableName,
|
|
341
|
+
columns,
|
|
342
|
+
foreignKeys
|
|
343
|
+
};
|
|
344
|
+
if (pkColumns.length > 0) {
|
|
345
|
+
table.primaryKey = { columns: pkColumns };
|
|
346
|
+
}
|
|
347
|
+
return table;
|
|
348
|
+
}
|
|
349
|
+
function convertTypeORMColumn(colArg, dialect) {
|
|
350
|
+
const options = colArg.options || {};
|
|
351
|
+
const isPrimary = colArg.mode === "regular" ? options.primary : colArg.mode === "objectId";
|
|
352
|
+
return {
|
|
353
|
+
name: options.name || colArg.propertyName,
|
|
354
|
+
type: mapTypeORMType(colArg, options, dialect),
|
|
355
|
+
nullable: options.nullable === true,
|
|
356
|
+
...isPrimary ? { primaryKey: true } : {},
|
|
357
|
+
...options.unique ? { unique: true } : {},
|
|
358
|
+
...options.default !== void 0 ? {
|
|
359
|
+
default: {
|
|
360
|
+
kind: typeof options.default === "string" && options.default.includes("(") ? "expression" : "value",
|
|
361
|
+
value: String(options.default)
|
|
362
|
+
}
|
|
363
|
+
} : {}
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function mapTypeORMType(colArg, options, dialect) {
|
|
367
|
+
const type = options.type || colArg.mode === "createDate" || colArg.mode === "updateDate" ? "timestamp" : "unknown";
|
|
368
|
+
switch (type.toLowerCase()) {
|
|
369
|
+
case "int":
|
|
370
|
+
case "integer":
|
|
371
|
+
case "int4":
|
|
372
|
+
return { kind: "int" };
|
|
373
|
+
case "smallint":
|
|
374
|
+
case "int2":
|
|
375
|
+
return { kind: "int", size: "smallint" };
|
|
376
|
+
case "bigint":
|
|
377
|
+
case "int8":
|
|
378
|
+
return { kind: "int", size: "bigint" };
|
|
379
|
+
case "varchar":
|
|
380
|
+
case "character varying":
|
|
381
|
+
return { kind: "text", length: options.length || 255 };
|
|
382
|
+
case "text":
|
|
383
|
+
return { kind: "text" };
|
|
384
|
+
case "boolean":
|
|
385
|
+
case "bool":
|
|
386
|
+
return { kind: "boolean" };
|
|
387
|
+
case "timestamp":
|
|
388
|
+
case "timestamp with time zone":
|
|
389
|
+
case "timestamptz":
|
|
390
|
+
return { kind: "timestamp", withTimezone: dialect === "pg" };
|
|
391
|
+
case "timestamp without time zone":
|
|
392
|
+
return { kind: "timestamp" };
|
|
393
|
+
case "date":
|
|
394
|
+
return { kind: "date" };
|
|
395
|
+
case "json":
|
|
396
|
+
return { kind: "json" };
|
|
397
|
+
case "jsonb":
|
|
398
|
+
return { kind: "json", binary: true };
|
|
399
|
+
case "uuid":
|
|
400
|
+
return { kind: "uuid" };
|
|
401
|
+
case "float":
|
|
402
|
+
case "real":
|
|
403
|
+
case "float4":
|
|
404
|
+
return { kind: "float" };
|
|
405
|
+
case "double precision":
|
|
406
|
+
case "float8":
|
|
407
|
+
return { kind: "float", size: "double" };
|
|
408
|
+
case "decimal":
|
|
409
|
+
case "numeric":
|
|
410
|
+
return {
|
|
411
|
+
kind: "decimal",
|
|
412
|
+
precision: options.precision,
|
|
413
|
+
scale: options.scale
|
|
414
|
+
};
|
|
415
|
+
case "bytea":
|
|
416
|
+
case "blob":
|
|
417
|
+
return { kind: "bytea" };
|
|
418
|
+
case "enum":
|
|
419
|
+
return { kind: "enum", enumName: options.enum?.name || "unknown" };
|
|
420
|
+
default:
|
|
421
|
+
return { kind: "custom", raw: type };
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
export {
|
|
425
|
+
exportDrizzleSchema,
|
|
426
|
+
exportPrismaSchema,
|
|
427
|
+
exportTypeORMSchema
|
|
428
|
+
};
|