Skip to content

Commit

Permalink
feat: generate separate insert types (#3)
Browse files Browse the repository at this point in the history
* feat: add support for generating separate insert types

* docs: update readme

* fix: skip insert types for views

* docs: update readme

* docs: update readme

* docs: update readme

* refactor: option description

* test: fix tests

* test: fix connection string init

* test: add tests for generating insert types

* fix: add missing ts types
  • Loading branch information
aldis-ameriks authored Nov 9, 2020
1 parent 30e7054 commit edb9d0e
Show file tree
Hide file tree
Showing 14 changed files with 2,256 additions and 13 deletions.
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Options:
--pascal-enums transform enum keys to pascal case (default: false)
--bigint use bigint for int8 types instead of strings (default: false)
--date-as-string use string for date types instead of javascript Date object (default: false)
--insert-types generate separate insert types with optional fields for columns allowing NULL value or having default values (default: false)
--help display help for command
Example:
Expand Down Expand Up @@ -85,6 +86,52 @@ interface UserEntity {

> By default, the types will be generated based on how [pg](https://github.com/brianc/node-postgres) returns the values.
#### Insert types
To simplify database inserts, separate types can be generated with optional values where NULL is allowed or default values for column exist in postgres.

Given database table
```sql
CREATE TYPE user_state AS ENUM (
'asleep',
'awake'
);

CREATE TABLE users (
id int4 NOT NULL,
name varchar(255) NOT NULL,
state user_state,
is_enabled bool NOT NULL DEFAULT FALSE
);
```

Running `pg-typegen -o ./entities.ts --insert-types postgres://username:password@localhost:5432/database`
Will generate the following type definitions
```ts
enum UserState {
asleep = 'asleep',
awake = 'awake'
}

interface UserEntity {
id: number;
name: string;
state: UserState | null;
is_enabled: boolean;
}

interface UserInsertEntity {
id?: number;
name: string;
state?: UserState | null;
is_enabled?: boolean;
}
```

Which should allow simplified inserts when coupled with libraries like [knex](https://github.com/knex/knex)
```ts
knex<UserInsertEntity>('users').insert({ name: 'foo' })
```

### Running from code

```ts
Expand Down
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface Options {
bigint?: boolean;
dateAsString?: boolean;
ssl?: boolean;
insertTypes?: boolean;
}

declare function generate(option: Options): Promise<string>;
Expand Down
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ program
.option('--pascal-enums', 'transform enum keys to pascal case', false)
.option('--bigint', 'use bigint for int8 types instead of strings', false)
.option('--date-as-string', 'use string for date types instead of javascript Date object', false)
.option('--insert-types', 'generate separate insert types with optional fields for columns allowing NULL value or having default values', false)

program.on('--help', () => {
console.log('')
Expand Down
12 changes: 8 additions & 4 deletions lib/postgres.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,19 @@ const typeMapping = {

function getTableDefinitions (sql, schema) {
return sql`
SELECT table_name AS name,
SELECT c.table_name AS name,
t.table_type = 'VIEW' AS "isView",
jsonb_agg(
DISTINCT jsonb_build_object(
'name', column_name,
'type', udt_name,
'hasDefault', column_default IS NOT NULL OR (is_identity = 'YES' AND identity_generation = 'ALWAYS'),
'isNullable', is_nullable::boolean
)) AS columns
FROM information_schema.columns
WHERE table_schema = ${schema}
GROUP BY table_name
FROM information_schema.columns c
INNER JOIN information_schema.tables t ON t.table_name = c.table_name
WHERE c.table_schema = ${schema}
GROUP BY c.table_name, t.table_type
`
}

Expand All @@ -40,6 +43,7 @@ async function getMaterializedViewDefinitions (sql, schema) {
WHERE schemaname = ${schema}
)
SELECT pc.relname AS name,
true AS "isView",
jsonb_agg(
DISTINCT jsonb_build_object(
'name', pa.attname,
Expand Down
24 changes: 20 additions & 4 deletions lib/typescript.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ function getColumnType (typeMapping, type, enums) {
return result[0]
}

function getTableType (opts, tableName) {
const { suffix } = opts

function getTableType (opts, tableName, suffix) {
let parsedTableName = toPascalCase(tableName)
if (parsedTableName.endsWith('ies')) {
parsedTableName = parsedTableName.slice(0, -3) + 'y'
Expand All @@ -86,7 +84,7 @@ function generateTableTypes (opts, tables, typeMapping, enums) {
.filter(table => !opts.exclude.includes(table.name))
.sort(sortByField('name'))
.map(table => {
let tableType = getTableType(opts, table.name)
let tableType = getTableType(opts, table.name, opts.suffix)

if (table.columns.length > 0) {
tableType += '\n'
Expand All @@ -99,6 +97,24 @@ function generateTableTypes (opts, tables, typeMapping, enums) {
})

tableType += `}${semicolon(opts)}\n`

if (opts.insertTypes && !table.isView) {
tableType += '\n'
tableType += getTableType(opts, table.name, `Insert${opts.suffix}`)

if (table.columns.length > 0) {
tableType += '\n'
}

table.columns
.sort(sortByField('name'))
.forEach(tableColumn => {
tableType += ` ${formatName(tableColumn.name)}${tableColumn.isNullable || tableColumn.hasDefault ? '?' : ''}: ${getColumnType(typeMapping, tableColumn.type, enums)}${tableColumn.isNullable && !opts.optionals ? ' | null' : ''}${semicolon(opts)}\n`
})

tableType += `}${semicolon(opts)}\n`
}

return tableType
}).join('\n')
}
Expand Down
Loading

0 comments on commit edb9d0e

Please sign in to comment.