Skip to content

Instantly share code, notes, and snippets.

@jycouet
Created August 4, 2023 13:11
Show Gist options
  • Save jycouet/581e6c4060217d4008099eacd890bca0 to your computer and use it in GitHub Desktop.
Save jycouet/581e6c4060217d4008099eacd890bca0 to your computer and use it in GitHub Desktop.
[REMULT] - getEntityTypescriptPostgres (Draft 2)
import {
existsSync,
lstatSync,
mkdir,
mkdirSync,
readdirSync,
rmdirSync,
unlinkSync,
writeFileSync
} from 'fs'
import { SqlDatabase, remult } from 'remult'
const deleteFolderRecursive = (path) => {
if (existsSync(path)) {
readdirSync(path).forEach((file) => {
const curPath = `${path}/${file}`
if (lstatSync(curPath).isDirectory()) {
// Recursive call for subdirectories
deleteFolderRecursive(curPath)
} else {
// Delete file
unlinkSync(curPath)
}
})
// Delete empty directory
rmdirSync(path)
} else {
}
}
const toPascalCase = (str) => {
return str.replace(/[-_](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase())
}
export async function getEntitiesTypescriptPostgres() {
const command = SqlDatabase.getDb(remult).createCommand()
const result = await command.execute(
`SELECT table_name FROM information_schema.tables WHERE table_schema='public';`
)
deleteFolderRecursive('./src/shared')
mkdirSync('./src/shared')
const toIgnore = ['PgStatStatements', 'PgStatStatementsInfo', 'PrismaMigrations']
result.rows.forEach(async (c) => {
const pascal = toPascalCase(c.table_name)
if (!toIgnore.includes(pascal)) {
try {
writeFileSync(`./src/shared/${pascal}.ts`, await getEntityTypescriptPostgres(c.table_name))
} catch (error) {
console.error(error)
}
}
})
}
export async function getEntityTypescriptPostgres(table: string, schema = 'public') {
const command = SqlDatabase.getDb(remult).createCommand()
const commandEnum = SqlDatabase.getDb(remult).createCommand()
let cols = ''
let enums = ''
let props = []
props.push('allowApiCrud: true')
if (table.toLocaleLowerCase() != table) {
// props.push("dbName: '\"" + table + "\"'");
}
let first: string = undefined!
for (const {
column_name,
column_default,
data_type,
datetime_precision,
character_maximum_length,
udt_name
} of (
await command.execute(
`select * from INFORMATION_SCHEMA.COLUMNS where table_schema=${command.addParameterAndReturnSqlToken(
schema
)} and table_name=${command.addParameterAndReturnSqlToken(table)}
order by ordinal_position`
)
).rows) {
let decorator = '@Fields.string'
let decoratorArgs = ''
let type = ''
let defaultVal = "''"
switch (data_type) {
case 'decimal':
case 'real':
case 'int':
case 'integer':
case 'smallint':
case 'tinyint':
case 'bigint':
case 'float':
case 'numeric':
case 'NUMBER':
case 'money':
case 'double precision':
if (column_default?.startsWith('nextval')) {
decorator = '@Fields.autoIncrement'
type = 'number'
defaultVal = null
} else {
if (datetime_precision === 0) {
decorator = '@Fields.integer'
} else {
decorator = '@Fields.number'
}
defaultVal = '0'
}
break
case 'nchar':
case 'nvarchar':
case 'ntext':
case 'NVARCHAR2':
case 'text':
case 'varchar':
case 'VARCHAR2':
case 'character varying':
break
case 'char':
case 'CHAR':
if (character_maximum_length == 8 && column_default == "('00000000')") {
decorator = '@Fields.dateOnly'
type = 'Date'
}
break
case 'date':
case 'DATE':
case 'datetime':
case 'datetime2':
case 'timestamp without time zone':
if (column_name === 'createdAt') {
decorator = '@Fields.createdAt'
type = 'Date'
defaultVal = 'new Date()'
} else if (column_name === 'updatedAt') {
decorator = '@Fields.updatedAt'
type = 'Date'
defaultVal = 'new Date()'
} else {
decorator = '@Fields.date'
type = 'Date'
}
break
case 'bit':
case 'boolean':
decorator = '@Fields.boolean'
break
case 'ARRAY':
decorator = '@Fields.object'
defaultVal = '[]'
if (udt_name === '_permission_enum') {
type = 'permission_enum[]'
const local_udt_name = 'permission_enum'
const enumDef1 = await commandEnum.execute(
`SELECT t.typname, e.enumlabel
FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid
WHERE t.typname = '${local_udt_name}'
ORDER BY t.typname, e.enumlabel;`
)
// console.log(`enumDef`, enumDef.rows)
enums += `export enum ${local_udt_name} {
${enumDef1.rows.map((e) => e.enumlabel).join(',\n ')}
}
`
}
break
case 'USER-DEFINED':
decorator = '@Fields.object'
if (column_default === null) {
defaultVal = 'null'
} else {
defaultVal = udt_name + '.' + column_default.split("'")[1]
}
const enumDef2 = await commandEnum.execute(
`SELECT t.typname, e.enumlabel
FROM pg_type t JOIN pg_enum e ON t.oid = e.enumtypid
WHERE t.typname = '${udt_name}'
ORDER BY t.typname, e.enumlabel;`
)
// console.log(`enumDef`, enumDef.rows)
enums += `export enum ${udt_name} {
${enumDef2.rows.map((e) => e.enumlabel).join(',\n ')}
}
`
break
default:
console.log('unmanaged', {
table,
column_name,
data_type,
character_maximum_length,
column_default,
udt_name
})
break
}
if (column_name.toLocaleLowerCase() != column_name || column_name == 'order')
decoratorArgs = `{ dbName: '"${column_name}"' }`
if (!first) first = column_name
cols += '\n\n ' + decorator + `(${decoratorArgs})\n ` + column_name
if (type) {
// cols += '!'
cols += ': '
cols += type
}
if (defaultVal) {
cols += ' = ' + defaultVal
}
}
// props.push(`defaultOrderBy: { ${first}: "asc" }`)
let r =
`import { Entity, Fields, EntityBase } from "remult";
@Entity<${toPascalCase(table)}>("${table}", { \n ${props.join(',\n ')} \n})
export class ${toPascalCase(table)} extends EntityBase {` +
cols +
'\n}'.replace(' ', '') +
`
${enums}`
return r
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment