@db-bridge/core 1.1.5 → 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 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,10 +81,15 @@ 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
  }
87
- var MIGRATION_PATTERN = /^(\d{14})_(.+)\.(ts|js|mjs)$/;
92
+ var MIGRATION_PATTERN = /^(?:([_a-z]+)_)?(\d{14})_(.+)\.(ts|js|mjs)$/;
88
93
  var MigrationLoader = class {
89
94
  directory;
90
95
  extensions;
@@ -111,12 +116,13 @@ var MigrationLoader = class {
111
116
  if (!match) {
112
117
  continue;
113
118
  }
114
- const [, timestamp, description] = match;
119
+ const [, prefix, timestamp, description] = match;
115
120
  files.push({
116
121
  name: basename(entry.name, ext),
117
122
  path: join(this.directory, entry.name),
118
123
  timestamp,
119
- description: description.replaceAll("_", " ")
124
+ description: description.replaceAll("_", " "),
125
+ prefix
120
126
  });
121
127
  }
122
128
  files.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
@@ -193,8 +199,10 @@ var MigrationLoader = class {
193
199
  }
194
200
  /**
195
201
  * Generate a new migration filename
202
+ * @param description - Migration description
203
+ * @param prefix - Optional prefix (e.g., 'auth' -> auth_20250119_xxx.ts)
196
204
  */
197
- static generateFilename(description) {
205
+ static generateFilename(description, prefix) {
198
206
  const now = /* @__PURE__ */ new Date();
199
207
  const timestamp = [
200
208
  now.getFullYear(),
@@ -205,7 +213,8 @@ var MigrationLoader = class {
205
213
  String(now.getSeconds()).padStart(2, "0")
206
214
  ].join("");
207
215
  const sanitizedDescription = description.toLowerCase().replaceAll(/[^\da-z]+/g, "_").replaceAll(/^_+|_+$/g, "");
208
- return `${timestamp}_${sanitizedDescription}.ts`;
216
+ const sanitizedPrefix = prefix ? prefix.toLowerCase().replaceAll(/[^\da-z]+/g, "_").replaceAll(/^_+|_+$/g, "") : null;
217
+ return sanitizedPrefix ? `${sanitizedPrefix}_${timestamp}_${sanitizedDescription}.ts` : `${timestamp}_${sanitizedDescription}.ts`;
209
218
  }
210
219
  /**
211
220
  * Get migration template content
@@ -1072,7 +1081,7 @@ var MigrationLock = class {
1072
1081
  }
1073
1082
  }
1074
1083
  sleep(ms) {
1075
- return new Promise((resolve7) => setTimeout(resolve7, ms));
1084
+ return new Promise((resolve8) => setTimeout(resolve8, ms));
1076
1085
  }
1077
1086
  };
1078
1087
 
@@ -1685,8 +1694,11 @@ var ForeignKeyChain = class {
1685
1694
  var SchemaBuilder = class {
1686
1695
  dialectInstance;
1687
1696
  adapter;
1697
+ collectMode;
1698
+ collectedStatements = [];
1688
1699
  constructor(options) {
1689
1700
  this.adapter = options.adapter;
1701
+ this.collectMode = options.collectMode ?? false;
1690
1702
  switch (options.dialect) {
1691
1703
  case "mysql": {
1692
1704
  this.dialectInstance = new MySQLDialect();
@@ -1792,6 +1804,14 @@ var SchemaBuilder = class {
1792
1804
  * Execute SQL statement
1793
1805
  */
1794
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
+ }
1795
1815
  if (this.adapter) {
1796
1816
  await this.adapter.execute(sql, params);
1797
1817
  } else {
@@ -1801,6 +1821,18 @@ var SchemaBuilder = class {
1801
1821
  }
1802
1822
  }
1803
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
+ }
1804
1836
  /**
1805
1837
  * Execute SQL query
1806
1838
  */
@@ -2256,7 +2288,23 @@ var FileMigrationRunner = class {
2256
2288
  const action = direction === "up" ? "Running" : "Rolling back";
2257
2289
  this.options.logger.info(`${action}: ${migration.name}`);
2258
2290
  if (this.options.dryRun) {
2259
- this.options.logger.info(`DRY RUN: Would ${direction} ${migration.name}`);
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
+ }
2260
2308
  return;
2261
2309
  }
2262
2310
  const transaction = migration.transactional ? await this.adapter.beginTransaction() : null;
@@ -2424,6 +2472,263 @@ async function freshCommand(options = {}) {
2424
2472
  }
2425
2473
  }
2426
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
+
2427
2732
  // src/cli/commands/latest.ts
2428
2733
  async function latestCommand(options = {}) {
2429
2734
  let adapter;
@@ -2460,7 +2765,8 @@ async function makeCommand(name) {
2460
2765
  await mkdir(directory, { recursive: true });
2461
2766
  console.log(`Created migrations directory: ${directory}`);
2462
2767
  }
2463
- const filename = MigrationLoader.generateFilename(name);
2768
+ const prefix = config.migrations?.prefix;
2769
+ const filename = MigrationLoader.generateFilename(name, prefix);
2464
2770
  const filepath = resolve(directory, filename);
2465
2771
  const migrationName = filename.replace(/\.(ts|js|mjs)$/, "");
2466
2772
  const content = MigrationLoader.getMigrationTemplate(migrationName);
@@ -2476,20 +2782,61 @@ async function makeCommand(name) {
2476
2782
  process.exit(1);
2477
2783
  }
2478
2784
  }
2785
+ var DEFAULT_PRIORITY = 100;
2479
2786
  var SeederLoader = class {
2480
2787
  constructor(directory) {
2481
2788
  this.directory = directory;
2482
2789
  }
2483
2790
  /**
2484
- * Load all seeder files from directory
2791
+ * Load all seeder files from directory (sorted by priority/dependencies)
2485
2792
  */
2486
2793
  async loadAll() {
2487
2794
  const files = await readdir(this.directory);
2488
- const seederFiles = files.filter((f) => /\.(ts|js|mjs)$/.test(f) && !f.endsWith(".d.ts")).sort().map((f) => ({
2795
+ const seederPaths = files.filter((f) => /\.(ts|js|mjs)$/.test(f) && !f.endsWith(".d.ts")).map((f) => ({
2489
2796
  name: this.getSeederName(f),
2490
2797
  path: resolve(this.directory, f)
2491
2798
  }));
2492
- return seederFiles;
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;
2493
2840
  }
2494
2841
  /**
2495
2842
  * Load a specific seeder by path
@@ -2511,10 +2858,13 @@ var SeederLoader = class {
2511
2858
  }
2512
2859
  /**
2513
2860
  * Generate a new seeder filename
2861
+ * @param name - Seeder name
2862
+ * @param prefix - Optional prefix (e.g., 'auth' -> auth_users_seeder.ts)
2514
2863
  */
2515
- static generateFilename(name) {
2864
+ static generateFilename(name, prefix) {
2516
2865
  const snakeName = name.replaceAll(/([a-z])([A-Z])/g, "$1_$2").replaceAll(/[\s-]+/g, "_").toLowerCase();
2517
- return `${snakeName}_seeder.ts`;
2866
+ const sanitizedPrefix = prefix ? prefix.toLowerCase().replaceAll(/[^\da-z]+/g, "_").replaceAll(/^_+|_+$/g, "") : null;
2867
+ return sanitizedPrefix ? `${sanitizedPrefix}_${snakeName}_seeder.ts` : `${snakeName}_seeder.ts`;
2518
2868
  }
2519
2869
  /**
2520
2870
  * Get seeder template
@@ -2528,10 +2878,16 @@ var SeederLoader = class {
2528
2878
  import type { Seeder, DatabaseAdapter } from '@db-bridge/core';
2529
2879
 
2530
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
+
2531
2887
  async run(adapter: DatabaseAdapter): Promise<void> {
2532
2888
  // Insert seed data
2533
2889
  // await adapter.execute(\`
2534
- // INSERT INTO users (name, email) VALUES
2890
+ // INSERT INTO ${name} (name, email) VALUES
2535
2891
  // ('John Doe', 'john@example.com'),
2536
2892
  // ('Jane Doe', 'jane@example.com')
2537
2893
  // \`);
@@ -2550,7 +2906,8 @@ async function makeSeederCommand(name) {
2550
2906
  await mkdir(directory, { recursive: true });
2551
2907
  console.log(`Created seeds directory: ${directory}`);
2552
2908
  }
2553
- const filename = SeederLoader.generateFilename(name);
2909
+ const prefix = config.seeds?.prefix;
2910
+ const filename = SeederLoader.generateFilename(name, prefix);
2554
2911
  const filepath = resolve(directory, filename);
2555
2912
  const seederName = filename.replace(/\.(ts|js|mjs)$/, "");
2556
2913
  const content = SeederLoader.getSeederTemplate(seederName);
@@ -2865,12 +3222,20 @@ Seed Commands:
2865
3222
  db:seed Run database seeders
2866
3223
  db:seed --class=<name> Run a specific seeder
2867
3224
 
3225
+ Type Generation:
3226
+ generate:types Generate TypeScript interfaces from schema
3227
+
2868
3228
  Options:
2869
3229
  --help, -h Show this help message
2870
3230
  --version, -v Show version number
2871
3231
  --dry-run Show what would be done without executing
2872
3232
  --step=<n> Number of batches to rollback (for rollback command)
2873
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)
2874
3239
 
2875
3240
  Examples:
2876
3241
  db-bridge migrate:make create_users_table
@@ -2879,6 +3244,8 @@ Examples:
2879
3244
  db-bridge make:seeder users
2880
3245
  db-bridge db:seed
2881
3246
  db-bridge db:seed --class=users
3247
+ db-bridge generate:types
3248
+ db-bridge generate:types --output=./src/types/db.ts --camel-case
2882
3249
  `;
2883
3250
  async function main() {
2884
3251
  const args = process.argv.slice(2);
@@ -2893,7 +3260,12 @@ async function main() {
2893
3260
  version: { type: "boolean", short: "v" },
2894
3261
  "dry-run": { type: "boolean" },
2895
3262
  step: { type: "string" },
2896
- 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" }
2897
3269
  },
2898
3270
  allowPositionals: true
2899
3271
  });
@@ -2902,7 +3274,12 @@ async function main() {
2902
3274
  version: values.version,
2903
3275
  dryRun: values["dry-run"],
2904
3276
  step: values.step ? parseInt(values.step, 10) : void 0,
2905
- 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
2906
3283
  };
2907
3284
  command = positionals[0] || "";
2908
3285
  commandArgs = positionals.slice(1);
@@ -2912,6 +3289,8 @@ async function main() {
2912
3289
  options.help = args.includes("--help") || args.includes("-h");
2913
3290
  options.version = args.includes("--version") || args.includes("-v");
2914
3291
  options.dryRun = args.includes("--dry-run");
3292
+ options.camelCase = args.includes("--camel-case");
3293
+ options.comments = args.includes("--comments");
2915
3294
  const stepArg = args.find((a) => a.startsWith("--step="));
2916
3295
  if (stepArg) {
2917
3296
  options.step = parseInt(stepArg.split("=")[1] || "1", 10);
@@ -2920,6 +3299,18 @@ async function main() {
2920
3299
  if (classArg) {
2921
3300
  options.class = classArg.split("=")[1];
2922
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
+ }
2923
3314
  }
2924
3315
  if (options.help || command === "help") {
2925
3316
  console.log(HELP);
@@ -2981,6 +3372,16 @@ async function main() {
2981
3372
  await seedCommand({ class: options.class });
2982
3373
  break;
2983
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
+ }
2984
3385
  default: {
2985
3386
  console.error(`Unknown command: ${command}`);
2986
3387
  console.log('Run "db-bridge --help" for usage information.');