@db-bridge/core 1.1.7 → 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/LICENSE +20 -20
- package/dist/cli/index.js +401 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +159 -112
- package/dist/index.js +313 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Berke Erdoğan
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Berke Erdoğan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
package/dist/cli/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { parseArgs } from 'util';
|
|
3
|
-
import { resolve, basename, extname, join } from 'path';
|
|
3
|
+
import { resolve, dirname, basename, extname, join } from 'path';
|
|
4
4
|
import { existsSync } from 'fs';
|
|
5
5
|
import { pathToFileURL } from 'url';
|
|
6
6
|
import { createHash } from 'crypto';
|
|
@@ -81,6 +81,11 @@ function applyDefaults(config) {
|
|
|
81
81
|
seeds: {
|
|
82
82
|
directory: "./src/seeds",
|
|
83
83
|
...config.seeds
|
|
84
|
+
},
|
|
85
|
+
types: {
|
|
86
|
+
output: "./src/types/database.ts",
|
|
87
|
+
camelCase: false,
|
|
88
|
+
...config.types
|
|
84
89
|
}
|
|
85
90
|
};
|
|
86
91
|
}
|
|
@@ -1076,7 +1081,7 @@ var MigrationLock = class {
|
|
|
1076
1081
|
}
|
|
1077
1082
|
}
|
|
1078
1083
|
sleep(ms) {
|
|
1079
|
-
return new Promise((
|
|
1084
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
1080
1085
|
}
|
|
1081
1086
|
};
|
|
1082
1087
|
|
|
@@ -1689,8 +1694,11 @@ var ForeignKeyChain = class {
|
|
|
1689
1694
|
var SchemaBuilder = class {
|
|
1690
1695
|
dialectInstance;
|
|
1691
1696
|
adapter;
|
|
1697
|
+
collectMode;
|
|
1698
|
+
collectedStatements = [];
|
|
1692
1699
|
constructor(options) {
|
|
1693
1700
|
this.adapter = options.adapter;
|
|
1701
|
+
this.collectMode = options.collectMode ?? false;
|
|
1694
1702
|
switch (options.dialect) {
|
|
1695
1703
|
case "mysql": {
|
|
1696
1704
|
this.dialectInstance = new MySQLDialect();
|
|
@@ -1796,6 +1804,14 @@ var SchemaBuilder = class {
|
|
|
1796
1804
|
* Execute SQL statement
|
|
1797
1805
|
*/
|
|
1798
1806
|
async execute(sql, params) {
|
|
1807
|
+
if (this.collectMode) {
|
|
1808
|
+
let statement = sql;
|
|
1809
|
+
if (params && params.length > 0) {
|
|
1810
|
+
statement += ` -- params: ${JSON.stringify(params)}`;
|
|
1811
|
+
}
|
|
1812
|
+
this.collectedStatements.push(statement);
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1799
1815
|
if (this.adapter) {
|
|
1800
1816
|
await this.adapter.execute(sql, params);
|
|
1801
1817
|
} else {
|
|
@@ -1805,6 +1821,18 @@ var SchemaBuilder = class {
|
|
|
1805
1821
|
}
|
|
1806
1822
|
}
|
|
1807
1823
|
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Get collected SQL statements (only in collectMode)
|
|
1826
|
+
*/
|
|
1827
|
+
getCollectedStatements() {
|
|
1828
|
+
return [...this.collectedStatements];
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Clear collected SQL statements
|
|
1832
|
+
*/
|
|
1833
|
+
clearCollectedStatements() {
|
|
1834
|
+
this.collectedStatements = [];
|
|
1835
|
+
}
|
|
1808
1836
|
/**
|
|
1809
1837
|
* Execute SQL query
|
|
1810
1838
|
*/
|
|
@@ -2260,7 +2288,23 @@ var FileMigrationRunner = class {
|
|
|
2260
2288
|
const action = direction === "up" ? "Running" : "Rolling back";
|
|
2261
2289
|
this.options.logger.info(`${action}: ${migration.name}`);
|
|
2262
2290
|
if (this.options.dryRun) {
|
|
2263
|
-
|
|
2291
|
+
const schema = new SchemaBuilder({
|
|
2292
|
+
dialect: this.options.dialect,
|
|
2293
|
+
collectMode: true
|
|
2294
|
+
});
|
|
2295
|
+
await migration[direction](schema);
|
|
2296
|
+
const statements = schema.getCollectedStatements();
|
|
2297
|
+
if (statements.length > 0) {
|
|
2298
|
+
this.options.logger.info(`DRY RUN: ${direction.toUpperCase()} ${migration.name}`);
|
|
2299
|
+
this.options.logger.info("SQL statements that would be executed:");
|
|
2300
|
+
for (const sql of statements) {
|
|
2301
|
+
this.options.logger.info(` ${sql}`);
|
|
2302
|
+
}
|
|
2303
|
+
} else {
|
|
2304
|
+
this.options.logger.info(
|
|
2305
|
+
`DRY RUN: ${direction.toUpperCase()} ${migration.name} (no SQL statements)`
|
|
2306
|
+
);
|
|
2307
|
+
}
|
|
2264
2308
|
return;
|
|
2265
2309
|
}
|
|
2266
2310
|
const transaction = migration.transactional ? await this.adapter.beginTransaction() : null;
|
|
@@ -2428,6 +2472,263 @@ async function freshCommand(options = {}) {
|
|
|
2428
2472
|
}
|
|
2429
2473
|
}
|
|
2430
2474
|
|
|
2475
|
+
// src/types/TypeGenerator.ts
|
|
2476
|
+
var SQL_TYPE_MAP = {
|
|
2477
|
+
// Integers
|
|
2478
|
+
tinyint: "number",
|
|
2479
|
+
smallint: "number",
|
|
2480
|
+
mediumint: "number",
|
|
2481
|
+
int: "number",
|
|
2482
|
+
integer: "number",
|
|
2483
|
+
bigint: "number",
|
|
2484
|
+
// Floats
|
|
2485
|
+
float: "number",
|
|
2486
|
+
double: "number",
|
|
2487
|
+
decimal: "number",
|
|
2488
|
+
numeric: "number",
|
|
2489
|
+
real: "number",
|
|
2490
|
+
// Strings
|
|
2491
|
+
char: "string",
|
|
2492
|
+
varchar: "string",
|
|
2493
|
+
tinytext: "string",
|
|
2494
|
+
text: "string",
|
|
2495
|
+
mediumtext: "string",
|
|
2496
|
+
longtext: "string",
|
|
2497
|
+
enum: "string",
|
|
2498
|
+
set: "string",
|
|
2499
|
+
// Binary
|
|
2500
|
+
binary: "Buffer",
|
|
2501
|
+
varbinary: "Buffer",
|
|
2502
|
+
tinyblob: "Buffer",
|
|
2503
|
+
blob: "Buffer",
|
|
2504
|
+
mediumblob: "Buffer",
|
|
2505
|
+
longblob: "Buffer",
|
|
2506
|
+
// Date/Time
|
|
2507
|
+
date: "Date",
|
|
2508
|
+
datetime: "Date",
|
|
2509
|
+
timestamp: "Date",
|
|
2510
|
+
time: "string",
|
|
2511
|
+
year: "number",
|
|
2512
|
+
// Boolean
|
|
2513
|
+
boolean: "boolean",
|
|
2514
|
+
bool: "boolean",
|
|
2515
|
+
// JSON
|
|
2516
|
+
json: "Record<string, unknown>",
|
|
2517
|
+
jsonb: "Record<string, unknown>",
|
|
2518
|
+
// UUID (PostgreSQL)
|
|
2519
|
+
uuid: "string",
|
|
2520
|
+
// Arrays (PostgreSQL)
|
|
2521
|
+
array: "unknown[]"
|
|
2522
|
+
};
|
|
2523
|
+
var TypeGenerator = class {
|
|
2524
|
+
constructor(adapter, dialect) {
|
|
2525
|
+
this.adapter = adapter;
|
|
2526
|
+
this.dialect = dialect;
|
|
2527
|
+
}
|
|
2528
|
+
/**
|
|
2529
|
+
* Generate TypeScript interfaces from database schema
|
|
2530
|
+
*/
|
|
2531
|
+
async generate(options = {}) {
|
|
2532
|
+
const tables = await this.getTables(options);
|
|
2533
|
+
const output = [];
|
|
2534
|
+
if (options.header) {
|
|
2535
|
+
output.push(options.header);
|
|
2536
|
+
output.push("");
|
|
2537
|
+
} else {
|
|
2538
|
+
output.push("/**");
|
|
2539
|
+
output.push(" * Auto-generated TypeScript types from database schema");
|
|
2540
|
+
output.push(` * Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
2541
|
+
output.push(" * DO NOT EDIT - This file is auto-generated");
|
|
2542
|
+
output.push(" */");
|
|
2543
|
+
output.push("");
|
|
2544
|
+
}
|
|
2545
|
+
for (const table of tables) {
|
|
2546
|
+
const tableInfo = await this.getTableInfo(table);
|
|
2547
|
+
const interfaceCode = this.generateInterface(tableInfo, options);
|
|
2548
|
+
output.push(interfaceCode);
|
|
2549
|
+
output.push("");
|
|
2550
|
+
}
|
|
2551
|
+
return output.join("\n");
|
|
2552
|
+
}
|
|
2553
|
+
/**
|
|
2554
|
+
* Get list of tables from database
|
|
2555
|
+
*/
|
|
2556
|
+
async getTables(options) {
|
|
2557
|
+
let tables;
|
|
2558
|
+
if (this.dialect === "mysql") {
|
|
2559
|
+
const result = await this.adapter.query(
|
|
2560
|
+
`SELECT table_name FROM information_schema.tables
|
|
2561
|
+
WHERE table_schema = DATABASE() AND table_type = 'BASE TABLE'`
|
|
2562
|
+
);
|
|
2563
|
+
tables = result.rows.map((r) => r["table_name"] || r["TABLE_NAME"]).filter((t) => t !== null && t !== void 0);
|
|
2564
|
+
} else {
|
|
2565
|
+
const result = await this.adapter.query(
|
|
2566
|
+
`SELECT table_name FROM information_schema.tables
|
|
2567
|
+
WHERE table_schema = 'public' AND table_type = 'BASE TABLE'`
|
|
2568
|
+
);
|
|
2569
|
+
tables = result.rows.map((r) => r["table_name"] || r["TABLE_NAME"]).filter((t) => t !== null && t !== void 0);
|
|
2570
|
+
}
|
|
2571
|
+
if (options.tables && options.tables.length > 0) {
|
|
2572
|
+
tables = tables.filter((t) => options.tables.includes(t));
|
|
2573
|
+
}
|
|
2574
|
+
if (options.exclude && options.exclude.length > 0) {
|
|
2575
|
+
tables = tables.filter((t) => !options.exclude.includes(t));
|
|
2576
|
+
}
|
|
2577
|
+
tables = tables.filter((t) => !t.startsWith("db_migrations") && !t.startsWith("db_bridge"));
|
|
2578
|
+
return tables.sort();
|
|
2579
|
+
}
|
|
2580
|
+
/**
|
|
2581
|
+
* Get column information for a table
|
|
2582
|
+
*/
|
|
2583
|
+
async getTableInfo(tableName) {
|
|
2584
|
+
let columns;
|
|
2585
|
+
if (this.dialect === "mysql") {
|
|
2586
|
+
columns = await this.getMySQLColumns(tableName);
|
|
2587
|
+
} else {
|
|
2588
|
+
columns = await this.getPostgreSQLColumns(tableName);
|
|
2589
|
+
}
|
|
2590
|
+
return { name: tableName, columns };
|
|
2591
|
+
}
|
|
2592
|
+
/**
|
|
2593
|
+
* Get MySQL column information
|
|
2594
|
+
*/
|
|
2595
|
+
async getMySQLColumns(tableName) {
|
|
2596
|
+
const result = await this.adapter.query(`SHOW FULL COLUMNS FROM \`${tableName}\``);
|
|
2597
|
+
return result.rows.map((row) => ({
|
|
2598
|
+
name: row.Field,
|
|
2599
|
+
type: this.parseColumnType(row.Type),
|
|
2600
|
+
nullable: row.Null === "YES",
|
|
2601
|
+
defaultValue: row.Default,
|
|
2602
|
+
isPrimary: row.Key === "PRI",
|
|
2603
|
+
isAutoIncrement: row.Extra.includes("auto_increment"),
|
|
2604
|
+
comment: row.Comment || void 0
|
|
2605
|
+
}));
|
|
2606
|
+
}
|
|
2607
|
+
/**
|
|
2608
|
+
* Get PostgreSQL column information
|
|
2609
|
+
*/
|
|
2610
|
+
async getPostgreSQLColumns(tableName) {
|
|
2611
|
+
const result = await this.adapter.query(
|
|
2612
|
+
`
|
|
2613
|
+
SELECT
|
|
2614
|
+
c.column_name,
|
|
2615
|
+
c.data_type,
|
|
2616
|
+
c.is_nullable,
|
|
2617
|
+
c.column_default,
|
|
2618
|
+
c.is_identity,
|
|
2619
|
+
pgd.description
|
|
2620
|
+
FROM information_schema.columns c
|
|
2621
|
+
LEFT JOIN pg_catalog.pg_statio_all_tables st ON c.table_name = st.relname
|
|
2622
|
+
LEFT JOIN pg_catalog.pg_description pgd ON pgd.objoid = st.relid
|
|
2623
|
+
AND pgd.objsubid = c.ordinal_position
|
|
2624
|
+
WHERE c.table_name = $1
|
|
2625
|
+
ORDER BY c.ordinal_position
|
|
2626
|
+
`,
|
|
2627
|
+
[tableName]
|
|
2628
|
+
);
|
|
2629
|
+
return result.rows.map((row) => ({
|
|
2630
|
+
name: row.column_name,
|
|
2631
|
+
type: row.data_type,
|
|
2632
|
+
nullable: row.is_nullable === "YES",
|
|
2633
|
+
defaultValue: row.column_default,
|
|
2634
|
+
isPrimary: false,
|
|
2635
|
+
// Would need additional query
|
|
2636
|
+
isAutoIncrement: row.is_identity === "YES" || (row.column_default?.includes("nextval") ?? false),
|
|
2637
|
+
comment: row.description
|
|
2638
|
+
}));
|
|
2639
|
+
}
|
|
2640
|
+
/**
|
|
2641
|
+
* Parse column type (extract base type from full type string)
|
|
2642
|
+
*/
|
|
2643
|
+
parseColumnType(fullType) {
|
|
2644
|
+
const baseType = fullType.toLowerCase().replace(/\(.*\)/, "").trim();
|
|
2645
|
+
return baseType.replace(" unsigned", "");
|
|
2646
|
+
}
|
|
2647
|
+
/**
|
|
2648
|
+
* Map SQL type to TypeScript type
|
|
2649
|
+
*/
|
|
2650
|
+
mapType(sqlType, nullable) {
|
|
2651
|
+
const baseType = this.parseColumnType(sqlType);
|
|
2652
|
+
if (sqlType.toLowerCase().includes("tinyint(1)")) {
|
|
2653
|
+
return nullable ? "boolean | null" : "boolean";
|
|
2654
|
+
}
|
|
2655
|
+
const tsType = SQL_TYPE_MAP[baseType] || "unknown";
|
|
2656
|
+
return nullable ? `${tsType} | null` : tsType;
|
|
2657
|
+
}
|
|
2658
|
+
/**
|
|
2659
|
+
* Generate TypeScript interface for a table
|
|
2660
|
+
*/
|
|
2661
|
+
generateInterface(table, options) {
|
|
2662
|
+
const lines = [];
|
|
2663
|
+
const interfaceName = this.toInterfaceName(table.name);
|
|
2664
|
+
if (options.includeComments) {
|
|
2665
|
+
lines.push("/**");
|
|
2666
|
+
lines.push(` * ${interfaceName} - ${table.name} table`);
|
|
2667
|
+
lines.push(" */");
|
|
2668
|
+
}
|
|
2669
|
+
lines.push(`export interface ${interfaceName} {`);
|
|
2670
|
+
for (const column of table.columns) {
|
|
2671
|
+
const propertyName = options.camelCase ? this.toCamelCase(column.name) : column.name;
|
|
2672
|
+
const tsType = this.mapType(column.type, column.nullable);
|
|
2673
|
+
const optional = options.optionalNullable && column.nullable ? "?" : "";
|
|
2674
|
+
if (options.includeComments && column.comment) {
|
|
2675
|
+
lines.push(` /** ${column.comment} */`);
|
|
2676
|
+
}
|
|
2677
|
+
lines.push(` ${propertyName}${optional}: ${tsType};`);
|
|
2678
|
+
}
|
|
2679
|
+
lines.push("}");
|
|
2680
|
+
return lines.join("\n");
|
|
2681
|
+
}
|
|
2682
|
+
/**
|
|
2683
|
+
* Convert table name to PascalCase interface name
|
|
2684
|
+
*/
|
|
2685
|
+
toInterfaceName(tableName) {
|
|
2686
|
+
return tableName.split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join("");
|
|
2687
|
+
}
|
|
2688
|
+
/**
|
|
2689
|
+
* Convert snake_case to camelCase
|
|
2690
|
+
*/
|
|
2691
|
+
toCamelCase(name) {
|
|
2692
|
+
return name.replaceAll(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
2693
|
+
}
|
|
2694
|
+
};
|
|
2695
|
+
|
|
2696
|
+
// src/cli/commands/generate-types.ts
|
|
2697
|
+
async function generateTypesCommand(options = {}) {
|
|
2698
|
+
let adapter;
|
|
2699
|
+
try {
|
|
2700
|
+
const config = await loadConfig();
|
|
2701
|
+
adapter = await createAdapterFromConfig(config);
|
|
2702
|
+
info("Generating TypeScript types from database schema...");
|
|
2703
|
+
console.log("");
|
|
2704
|
+
const generator = new TypeGenerator(adapter, config.connection.dialect);
|
|
2705
|
+
const types = await generator.generate({
|
|
2706
|
+
tables: options.tables ? options.tables.split(",").map((t) => t.trim()) : void 0,
|
|
2707
|
+
exclude: options.exclude ? options.exclude.split(",").map((t) => t.trim()) : void 0,
|
|
2708
|
+
camelCase: options.camelCase ?? false,
|
|
2709
|
+
includeComments: options.comments ?? true,
|
|
2710
|
+
optionalNullable: true
|
|
2711
|
+
});
|
|
2712
|
+
const outputPath = resolve(
|
|
2713
|
+
process.cwd(),
|
|
2714
|
+
options.output || config.types?.output || "./src/types/database.ts"
|
|
2715
|
+
);
|
|
2716
|
+
await mkdir(dirname(outputPath), { recursive: true });
|
|
2717
|
+
await writeFile(outputPath, types, "utf8");
|
|
2718
|
+
console.log("");
|
|
2719
|
+
success(`Types generated: ${outputPath}`);
|
|
2720
|
+
const interfaceCount = (types.match(/export interface/g) || []).length;
|
|
2721
|
+
info(`Generated ${interfaceCount} interface(s)`);
|
|
2722
|
+
} catch (error_) {
|
|
2723
|
+
error(error_.message);
|
|
2724
|
+
process.exit(1);
|
|
2725
|
+
} finally {
|
|
2726
|
+
if (adapter) {
|
|
2727
|
+
await adapter.disconnect();
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2431
2732
|
// src/cli/commands/latest.ts
|
|
2432
2733
|
async function latestCommand(options = {}) {
|
|
2433
2734
|
let adapter;
|
|
@@ -2481,20 +2782,61 @@ async function makeCommand(name) {
|
|
|
2481
2782
|
process.exit(1);
|
|
2482
2783
|
}
|
|
2483
2784
|
}
|
|
2785
|
+
var DEFAULT_PRIORITY = 100;
|
|
2484
2786
|
var SeederLoader = class {
|
|
2485
2787
|
constructor(directory) {
|
|
2486
2788
|
this.directory = directory;
|
|
2487
2789
|
}
|
|
2488
2790
|
/**
|
|
2489
|
-
* Load all seeder files from directory
|
|
2791
|
+
* Load all seeder files from directory (sorted by priority/dependencies)
|
|
2490
2792
|
*/
|
|
2491
2793
|
async loadAll() {
|
|
2492
2794
|
const files = await readdir(this.directory);
|
|
2493
|
-
const
|
|
2795
|
+
const seederPaths = files.filter((f) => /\.(ts|js|mjs)$/.test(f) && !f.endsWith(".d.ts")).map((f) => ({
|
|
2494
2796
|
name: this.getSeederName(f),
|
|
2495
2797
|
path: resolve(this.directory, f)
|
|
2496
2798
|
}));
|
|
2497
|
-
|
|
2799
|
+
const seederFiles = [];
|
|
2800
|
+
for (const file of seederPaths) {
|
|
2801
|
+
const seeder = await this.load(file.path);
|
|
2802
|
+
seederFiles.push({
|
|
2803
|
+
name: file.name,
|
|
2804
|
+
path: file.path,
|
|
2805
|
+
priority: seeder.priority ?? DEFAULT_PRIORITY,
|
|
2806
|
+
depends: seeder.depends
|
|
2807
|
+
});
|
|
2808
|
+
}
|
|
2809
|
+
return this.sortByDependencies(seederFiles);
|
|
2810
|
+
}
|
|
2811
|
+
/**
|
|
2812
|
+
* Sort seeders by dependencies (topological sort) then by priority
|
|
2813
|
+
*/
|
|
2814
|
+
sortByDependencies(files) {
|
|
2815
|
+
const fileMap = new Map(files.map((f) => [f.name, f]));
|
|
2816
|
+
const visited = /* @__PURE__ */ new Set();
|
|
2817
|
+
const result = [];
|
|
2818
|
+
const visit = (file) => {
|
|
2819
|
+
if (visited.has(file.name)) {
|
|
2820
|
+
return;
|
|
2821
|
+
}
|
|
2822
|
+
visited.add(file.name);
|
|
2823
|
+
if (file.depends) {
|
|
2824
|
+
for (const dep of file.depends) {
|
|
2825
|
+
const depFile = fileMap.get(dep) || fileMap.get(`${dep}_seeder`);
|
|
2826
|
+
if (depFile) {
|
|
2827
|
+
visit(depFile);
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
result.push(file);
|
|
2832
|
+
};
|
|
2833
|
+
const sortedByPriority = [...files].sort(
|
|
2834
|
+
(a, b) => (a.priority ?? DEFAULT_PRIORITY) - (b.priority ?? DEFAULT_PRIORITY)
|
|
2835
|
+
);
|
|
2836
|
+
for (const file of sortedByPriority) {
|
|
2837
|
+
visit(file);
|
|
2838
|
+
}
|
|
2839
|
+
return result;
|
|
2498
2840
|
}
|
|
2499
2841
|
/**
|
|
2500
2842
|
* Load a specific seeder by path
|
|
@@ -2536,10 +2878,16 @@ var SeederLoader = class {
|
|
|
2536
2878
|
import type { Seeder, DatabaseAdapter } from '@db-bridge/core';
|
|
2537
2879
|
|
|
2538
2880
|
export default {
|
|
2881
|
+
// Priority: lower runs first (default: 100)
|
|
2882
|
+
// priority: 10,
|
|
2883
|
+
|
|
2884
|
+
// Dependencies: seeders that must run before this one
|
|
2885
|
+
// depends: ['users'],
|
|
2886
|
+
|
|
2539
2887
|
async run(adapter: DatabaseAdapter): Promise<void> {
|
|
2540
2888
|
// Insert seed data
|
|
2541
2889
|
// await adapter.execute(\`
|
|
2542
|
-
// INSERT INTO
|
|
2890
|
+
// INSERT INTO ${name} (name, email) VALUES
|
|
2543
2891
|
// ('John Doe', 'john@example.com'),
|
|
2544
2892
|
// ('Jane Doe', 'jane@example.com')
|
|
2545
2893
|
// \`);
|
|
@@ -2874,12 +3222,20 @@ Seed Commands:
|
|
|
2874
3222
|
db:seed Run database seeders
|
|
2875
3223
|
db:seed --class=<name> Run a specific seeder
|
|
2876
3224
|
|
|
3225
|
+
Type Generation:
|
|
3226
|
+
generate:types Generate TypeScript interfaces from schema
|
|
3227
|
+
|
|
2877
3228
|
Options:
|
|
2878
3229
|
--help, -h Show this help message
|
|
2879
3230
|
--version, -v Show version number
|
|
2880
3231
|
--dry-run Show what would be done without executing
|
|
2881
3232
|
--step=<n> Number of batches to rollback (for rollback command)
|
|
2882
3233
|
--class=<name> Specific seeder class to run (for db:seed)
|
|
3234
|
+
--output=<path> Output file path (for generate:types)
|
|
3235
|
+
--tables=<list> Comma-separated list of tables to include
|
|
3236
|
+
--exclude=<list> Comma-separated list of tables to exclude
|
|
3237
|
+
--camel-case Use camelCase for property names
|
|
3238
|
+
--comments Include JSDoc comments (default: true)
|
|
2883
3239
|
|
|
2884
3240
|
Examples:
|
|
2885
3241
|
db-bridge migrate:make create_users_table
|
|
@@ -2888,6 +3244,8 @@ Examples:
|
|
|
2888
3244
|
db-bridge make:seeder users
|
|
2889
3245
|
db-bridge db:seed
|
|
2890
3246
|
db-bridge db:seed --class=users
|
|
3247
|
+
db-bridge generate:types
|
|
3248
|
+
db-bridge generate:types --output=./src/types/db.ts --camel-case
|
|
2891
3249
|
`;
|
|
2892
3250
|
async function main() {
|
|
2893
3251
|
const args = process.argv.slice(2);
|
|
@@ -2902,7 +3260,12 @@ async function main() {
|
|
|
2902
3260
|
version: { type: "boolean", short: "v" },
|
|
2903
3261
|
"dry-run": { type: "boolean" },
|
|
2904
3262
|
step: { type: "string" },
|
|
2905
|
-
class: { type: "string" }
|
|
3263
|
+
class: { type: "string" },
|
|
3264
|
+
output: { type: "string" },
|
|
3265
|
+
tables: { type: "string" },
|
|
3266
|
+
exclude: { type: "string" },
|
|
3267
|
+
"camel-case": { type: "boolean" },
|
|
3268
|
+
comments: { type: "boolean" }
|
|
2906
3269
|
},
|
|
2907
3270
|
allowPositionals: true
|
|
2908
3271
|
});
|
|
@@ -2911,7 +3274,12 @@ async function main() {
|
|
|
2911
3274
|
version: values.version,
|
|
2912
3275
|
dryRun: values["dry-run"],
|
|
2913
3276
|
step: values.step ? parseInt(values.step, 10) : void 0,
|
|
2914
|
-
class: values.class
|
|
3277
|
+
class: values.class,
|
|
3278
|
+
output: values.output,
|
|
3279
|
+
tables: values.tables,
|
|
3280
|
+
exclude: values.exclude,
|
|
3281
|
+
camelCase: values["camel-case"],
|
|
3282
|
+
comments: values.comments
|
|
2915
3283
|
};
|
|
2916
3284
|
command = positionals[0] || "";
|
|
2917
3285
|
commandArgs = positionals.slice(1);
|
|
@@ -2921,6 +3289,8 @@ async function main() {
|
|
|
2921
3289
|
options.help = args.includes("--help") || args.includes("-h");
|
|
2922
3290
|
options.version = args.includes("--version") || args.includes("-v");
|
|
2923
3291
|
options.dryRun = args.includes("--dry-run");
|
|
3292
|
+
options.camelCase = args.includes("--camel-case");
|
|
3293
|
+
options.comments = args.includes("--comments");
|
|
2924
3294
|
const stepArg = args.find((a) => a.startsWith("--step="));
|
|
2925
3295
|
if (stepArg) {
|
|
2926
3296
|
options.step = parseInt(stepArg.split("=")[1] || "1", 10);
|
|
@@ -2929,6 +3299,18 @@ async function main() {
|
|
|
2929
3299
|
if (classArg) {
|
|
2930
3300
|
options.class = classArg.split("=")[1];
|
|
2931
3301
|
}
|
|
3302
|
+
const outputArg = args.find((a) => a.startsWith("--output="));
|
|
3303
|
+
if (outputArg) {
|
|
3304
|
+
options.output = outputArg.split("=")[1];
|
|
3305
|
+
}
|
|
3306
|
+
const tablesArg = args.find((a) => a.startsWith("--tables="));
|
|
3307
|
+
if (tablesArg) {
|
|
3308
|
+
options.tables = tablesArg.split("=")[1];
|
|
3309
|
+
}
|
|
3310
|
+
const excludeArg = args.find((a) => a.startsWith("--exclude="));
|
|
3311
|
+
if (excludeArg) {
|
|
3312
|
+
options.exclude = excludeArg.split("=")[1];
|
|
3313
|
+
}
|
|
2932
3314
|
}
|
|
2933
3315
|
if (options.help || command === "help") {
|
|
2934
3316
|
console.log(HELP);
|
|
@@ -2990,6 +3372,16 @@ async function main() {
|
|
|
2990
3372
|
await seedCommand({ class: options.class });
|
|
2991
3373
|
break;
|
|
2992
3374
|
}
|
|
3375
|
+
case "generate:types": {
|
|
3376
|
+
await generateTypesCommand({
|
|
3377
|
+
output: options.output,
|
|
3378
|
+
tables: options.tables,
|
|
3379
|
+
exclude: options.exclude,
|
|
3380
|
+
camelCase: options.camelCase,
|
|
3381
|
+
comments: options.comments
|
|
3382
|
+
});
|
|
3383
|
+
break;
|
|
3384
|
+
}
|
|
2993
3385
|
default: {
|
|
2994
3386
|
console.error(`Unknown command: ${command}`);
|
|
2995
3387
|
console.log('Run "db-bridge --help" for usage information.');
|