Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const cli = meow(
Options
--table, -t Table name
--prefix, -p Prefix to add to table names
--schema-name, -s Overrides name of the database schema

Examples
$ mysql-schema-ts --prefix SQL
Expand All @@ -24,20 +25,25 @@ const cli = meow(
type: 'string',
alias: 'p',
default: ''
},
schemaName: {
type: 'string',
alias: 's',
default: ''
}
}
}
)

const db = cli.input[0]
const { table, prefix } = cli.flags
const { table, prefix, schemaName } = cli.flags

async function main(): Promise<string> {
if (table) {
return inferTable(db, table, prefix)
}

return inferSchema(db, prefix)
return inferSchema(db, schemaName ? schemaName : null, prefix)
}

main()
Expand Down
22 changes: 15 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { tableToTS, Table } from './typescript'
import { tableToTS, Table, schemaToTS } from './typescript'
import { MySQL } from './mysql-client'
import prettier from 'prettier'
import pkg from '../package.json'
Expand All @@ -25,21 +25,29 @@ const header = (includesJSON: boolean): string => `
${includesJSON ? JSONHeader : ''}
`

export async function inferTable(connectionString: string, table: string, prefix?: string): Promise<string> {
export async function inferTable(connectionString: string, table: string, prefix = ''): Promise<string> {
const db = new MySQL(connectionString)
const code = tableToTS(table, prefix || '', await db.table(table))
const code = tableToTS(table, prefix, await db.table(table))
const fullCode = `
${header(code.includes('JSON'))}
${code}
`
return pretty(fullCode)
}

export async function inferSchema(connectionString: string, prefix?: string): Promise<string> {
export async function inferSchema(
connectionString: string,
schemaName: string | null = null,
prefix = ''
): Promise<string> {
const db = new MySQL(connectionString)
const tables = await db.allTables()
const interfaces = tables.map(table => tableToTS(table.name, prefix || '', table.table))
const code = [header(interfaces.some(i => i.includes('JSON'))), ...interfaces].join('\n')
const interfaces = tables.map(table => tableToTS(table.name, prefix, table.table))
const code = [
header(interfaces.some(i => i.includes('JSON'))),
...interfaces,
schemaToTS(schemaName ?? `${db.databaseName}Schema`, prefix, tables)
].join('\n\n')
return pretty(code)
}

Expand All @@ -48,7 +56,7 @@ export async function inferTableObject(connectionString: string, table: string):
return db.table(table)
}

export async function inferSchemaObject(connectionString: string) {
export async function inferSchemaObject(connectionString: string): Promise<{ name: string; table: Table }[]> {
const db = new MySQL(connectionString)
const tables = await db.allTables()
return tables
Expand Down
17 changes: 6 additions & 11 deletions src/mysql-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,17 @@ export function query<T>(conn: Connection, sql: SQLStatement): Promise<T[]> {
}

export class MySQL {
private connection: Connection
private defaultSchema: string
private readonly connection: Connection
public readonly databaseName: string

constructor(connectionString: string) {
this.connection = createConnection(connectionString)
const database = urlParse(connectionString, true).pathname?.substr(1) || 'public'
this.defaultSchema = database
this.databaseName = urlParse(connectionString, true).pathname?.substr(1) || 'public'
}

public async table(tableName: string): Promise<Table> {
const enumTypes = await this.enums(tableName)
const table = await this.getTable(tableName, this.schema())
const table = await this.getTable(tableName, this.databaseName)
return mapColumn(table, enumTypes)
}

Expand All @@ -75,17 +74,13 @@ export class MySQL {
this.connection,
sql`SELECT table_name as table_name
FROM information_schema.columns
WHERE table_schema = ${this.schema()}
WHERE table_schema = ${this.databaseName}
GROUP BY table_name
`
)
return schemaTables.map(schemaItem => schemaItem.table_name)
}

public schema(): string {
return this.defaultSchema
}

private async enums(tableName: string): Promise<Enums> {
const enums: Enums = {}

Expand All @@ -97,7 +92,7 @@ export class MySQL {
data_type as data_type
FROM information_schema.columns
WHERE data_type IN ('enum', 'set')
AND table_schema = ${this.schema()}
AND table_schema = ${this.databaseName}
AND table_name = ${tableName}`
)

Expand Down
27 changes: 26 additions & 1 deletion src/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function normalize(name: string): string {

export function tableToTS(name: string, prefix: string, table: Table): string {
const members = (withDefaults: boolean): string[] =>
Object.keys(table).map((column) => {
Object.keys(table).map(column => {
const type = table[column].tsType
const nullable = table[column].nullable ? '| null' : ''
const defaultComment = table[column].defaultValue ? `Defaults to: ${table[column].defaultValue}.` : ''
Expand Down Expand Up @@ -67,3 +67,28 @@ export function tableToTS(name: string, prefix: string, table: Table): string {
}
`.trim()
}

export function schemaToTS(
name: string,
tablePrefix: string,
tables: {
name: string
table: Table
}[]
): string {
return `
/**
* Exposes database schema type that contains all table definitions
*/
export interface ${camelize(name)} {
${tables
.map(table => {
const tableTypeName = tablePrefix + camelize(normalize(table.name))

// don't prepend the prefix to the table name on the left (object key) because it's supposed to match the actual database table names
return `${table.name}: { simple: ${tableTypeName}; withDefaults: ${tableTypeName}WithDefaults; };`
})
.join('\n')}
}
`.trim()
}