├── .gitignore ├── README.md ├── docs ├── .nojekyll ├── README.md ├── classes │ └── Generator.md └── interfaces │ ├── CommonCustomTypesOptions.md │ ├── CommonOptions.md │ ├── CommonPrimaryKeyOptions.md │ ├── CommonTypeAdapterOptions.md │ ├── ExportOptions.md │ ├── FieldMapping.md │ ├── GeneratedField.md │ ├── GeneratedFieldType.md │ ├── GeneratorOpts.md │ ├── ImportedItem.md │ └── TableInclusion.md ├── package.json ├── pnpm-lock.yaml ├── src ├── cli.ts ├── field-mappings.ts ├── file-remover.ts ├── generator-options.ts ├── generator.ts ├── help.hbs ├── index.ts ├── logger.ts ├── matcher.ts ├── tbls-types.ts └── template.ts.hbs ├── test ├── __snapshots__ │ ├── Generator │ │ ├── allows-customizing-naming.expected.txt │ │ ├── allows-exporting-crud-repositories.expected.txt │ │ ├── allows-exporting-instances.expected.txt │ │ ├── allows-exporting-row-types-as-interface.expected.txt │ │ ├── allows-exporting-row-types.expected.txt │ │ ├── allows-exporting-value-types-as-interface.expected.txt │ │ ├── allows-exporting-value-types.expected.txt │ │ ├── allows-non-relative-and-default-import-paths-with-db-type-name.expected.txt │ │ ├── allows-non-relative-and-default-import-paths.expected.txt │ │ ├── allows-omitting-specific-tables-and-fields.expected.txt │ │ ├── allows-wrapping-exported-types.expected.txt │ │ ├── generates-code-from-schema-with-useQualifiedTablePrefix-false.expected.txt │ │ ├── generates-code-from-schema-with-useQualifiedTablePrefix-true.expected.txt │ │ ├── generates-code-from-schema-with-useQualifiedTablePrefix-undefined.expected.txt │ │ ├── generates-valid-import-on-inner-folder.expected.txt │ │ ├── supports-custom-comparable-field-with-db-type-name.expected.txt │ │ ├── supports-custom-comparable-field.expected.txt │ │ ├── supports-generating-column-type-mappings.expected.txt │ │ └── supports-injection-of-raw-content-in-generated-files.expected.txt │ ├── supports-custom-UUID-field-with-db-type-name.expected.txt │ ├── supports-custom-UUID-field.expected.txt │ ├── supports-custom-double-field-with-db-type-name.expected.txt │ ├── supports-custom-double-field.expected.txt │ ├── supports-custom-int-field-with-db-type-name.expected.txt │ ├── supports-custom-int-field.expected.txt │ ├── supports-custom-local-date-field-with-db-type-name.expected.txt │ ├── supports-custom-local-date-field.expected.txt │ ├── supports-custom-local-date-time-field-with-db-type-name.expected.txt │ ├── supports-custom-local-date-time-field.expected.txt │ ├── supports-custom-local-time-field-with-db-type-name.expected.txt │ └── supports-custom-local-time-field.expected.txt ├── data │ ├── test.schema.yaml │ └── test.sql ├── helpers │ ├── adapters.ts │ ├── connection-source.ts │ └── types.ts └── test.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ts-sql-codegen 2 | 3 | This utility generates [table mapper classes](https://ts-sql-query.readthedocs.io/en/stable/connection-tables-views/#mapping-the-tables) for [ts-sql-query](https://ts-sql-query.readthedocs.io/en/stable/) by inspecting a database through [tbls](https://github.com/k1LoW/tbls). 4 | 5 | While it is possible to use ts-sql-query without any code-generation, we have to manually keep the table-mappings in sync with the database which is burdensome. 6 | 7 | With this utility, this mapping can be derived from the database itself as part of build process, eliminating the manual effort, reducing room for erroneous mapping and enabling true end-to-end type-safety. 8 | 9 | The code-generation process is highly customizable and almost all the defaults (which are sensible for most databases) can be configured if needed. 10 | 11 | ## Features 12 | 13 | 1. Generate clean typescript table/view mappers from database schema 14 | 1. Ability to include/exclude tables/fields 15 | 1. Ability to customize field types and type adapters 16 | 1. Idiomatic pascal-cased/camel-cased table/field name mapping by default and ability to customize names if desired. 17 | 1. Auto-detection & mapping of computed fields, primary key columns. 18 | 1. Automatic documentation propagation from SQL comments 19 | 20 | ## Installation: 21 | 22 | **Step 1:** Install tbls and ensure it is available in path 23 | 24 | Refer: https://github.com/k1LoW/tbls#install 25 | 26 | **Step 2:** Install ts-sql-codegen 27 | 28 | `npm i --dev ts-sql-codegen` 29 | 30 | ### Note: 31 | 32 | - Global installation (`npm i -g ts-sql-codegen`) can be convenient, but is preferrable 33 | to have ts-sql-codegen as a project dependency to avoid versioning issues. 34 | 35 | ## Usage (CLI): 36 | 37 | After every database schema change: 38 | 39 | **Step 1:** Generate yaml schema file from database using tbls 40 | 41 | **Example:** `tbls out postgres://postgres:password@localhost:5432/testdb -t yaml -o schema.yaml` 42 | 43 | **Step 2:** Pass this schema file to ts-sql-codegen 44 | 45 | **Example:** `ts-sql-codegen --schema ./schema.yaml --output-dir ./src/generated --connection-source ./src/db/connection-source` 46 | 47 | ### Note: 48 | 49 | - All paths are relative to cwd 50 | - Above options are default, so you can also just run ts-sql-codegen 51 | 52 | ## Usage (Programmatic): 53 | 54 | Programmatic usage enables a wider set of customization options. 55 | 56 | **Example:** 57 | 58 | ```ts 59 | const { Generator } = require('ts-sql-codegen'); 60 | 61 | const options = { 62 | schemaPath: './schema.yaml', 63 | connectionSourcePath: './connection-source.ts' 64 | } 65 | const generator = new Generator(options); 66 | await generator.generate(); 67 | ``` 68 | 69 | Refer to [Generator](./docs/classes/Generator.md) and [GeneratorOpts](./docs/interfaces/GeneratorOpts.md) for available options. 70 | 71 | The [test suite](./test/test.ts) also has examples of more complex customizations. 72 | 73 | For advanced use-cases (eg. custom templates, pre/post processing of generated code 74 | and custom logic for table/column/field mapping) it is recommended to extend the Generator class in project. 75 | We intend to keep the customization options that the constructor accepts focussed on primary common use-cases. 76 | 77 | ## Suggested workflow 78 | 79 | This utility is expected to be used in a database-first workflow, where the developers first plan and implement the database level changes, and then adapt their code for the updated schema. 80 | 81 | 1. Use a database migration system for versioned database schema evolution. You are free to choose a migration utility that you like (eg. dbmate, liquibase, flyway etc.) - if you are starting out we recommend dbmate, a simple and easy to use solution. 82 | 1. Integrate the following steps into your build lifecycle 83 | 1. Use migration utility to update database schema 84 | eg. `dbmate up` 85 | 1. Dump yaml representation of schema through tbls 86 | eg. `tbls out postgres://localhost:5432/mydb -t yaml -o schema.yaml` 87 | 1. Generate code using ts-sql-codegen 88 | eg. `ts-sql-codegen --schema ./schema.yaml --remove-extraneous` 89 | 1. Use generated table mappers in your application 90 | 91 | ## Recipies 92 | 93 | ### Generating mappers for a subset of available tables or fields 94 | 95 | ```ts 96 | const options = { 97 | schemaPath, 98 | connectionSourcePath, 99 | outputDirPath, 100 | tables: { 101 | // Include only whitelisted tables 102 | include: [/authors/, "books"], 103 | // Similarly exclude can be used to blacklist 104 | }, 105 | fieldMappings: [ 106 | { 107 | tableName: "authors", 108 | columnName: "name", 109 | // Ignore this field when generating table mapper 110 | generatedField: false, 111 | } 112 | ] 113 | } 114 | ``` 115 | 116 | :warning: We don't do anything to ensure that database operations will succeed with included columns. Eg. if any omitted columns are mandatory they will cause inserts to fail. 117 | 118 | ### Custom DB types 119 | 120 | ts-sql-query supports custom database types through [type-adapters](https://ts-sql-query.readthedocs.io/en/stable/column-types/#type-adapters). 121 | 122 | You can configure the code-generator to use type-adapters for specific columns or specific database types through field mappings. 123 | 124 | ```ts 125 | const options = { 126 | schemaPath: './schema.yaml', 127 | connectionSourcePath: './connection-source.ts', 128 | fieldMappings: [ 129 | { 130 | // Field matching criteria: 131 | // 132 | // Match any column of any table where column type in database 133 | // is the class_participation_policy custom type 134 | columnType: "class_participation_policy", 135 | 136 | // For fields matched by above criteria, 137 | // use the ClassParticipationPolicyAdapter type adapter 138 | // which you will have to implement. 139 | // 140 | // The import paths are resolved relative to cwd, and converted 141 | // to relative paths wrt the generated file 142 | // 143 | // Generated code will include an import like this: 144 | // import { ClassParticipationPolicyAdapter, ClassParticipationPolicy } from '../adapters'; 145 | generatedField: { 146 | type: { 147 | kind: 'custom', 148 | adapter: { 149 | name: "ClassParticipationPolicyAdapter", 150 | importPath: "src/db/adapters", 151 | }, 152 | tsType: { 153 | name: "ClassParticipationPolicy", 154 | importPath: "src/db/adapters", 155 | }, 156 | dbType: { 157 | name: 'class_participation_policy' 158 | } 159 | }, 160 | }, 161 | }, 162 | { 163 | // Alternatively, the field matching criteria 164 | // can point to specific column of a specific table 165 | tableName: "classrooms", 166 | columnName: 'participation_policy', 167 | // Instead of exact strings above, we could also use regular expressions 168 | 169 | generatedField: { ... } 170 | } 171 | ] 172 | } 173 | ``` 174 | 175 | ### Multiple databases/schema 176 | 177 | The codegenerator does not have any special support for multi-db or multi-schema scenarios. 178 | 179 | You can simply run ts-sql-codegen multiple times for different databases/different schema. 180 | 181 | #### Filtering tables by schema 182 | 183 | The tbls schema dump contains table names with schema prefix. We can target this prefix in table inclusion criteria: 184 | 185 | ```ts 186 | const options = { 187 | tables: { 188 | include: [/^public\..*/] 189 | } 190 | } 191 | ``` 192 | 193 | This can be helpful, for instance, if we want tables from different schema to be generated with different configurations or different output directories. 194 | 195 | #### Disambiguating tables in multi-schema scenarios 196 | 197 | Use of `tableMapping.useQualifiedTableName=true` is recommended when the application can simultaneously use tables from multiple schema 198 | 199 | ```ts 200 | const options = { 201 | tableMapping: { 202 | useQualifiedTableName: true 203 | } 204 | } 205 | ``` 206 | 207 | With this option the output looks like: 208 | 209 | ```ts 210 | export class AuthorsTable extends Table { 211 | // ~~~~~~ 212 | // ^ 213 | // .. fields ... 214 | constructor() { 215 | super('public.authors') 216 | // ~~~~~~~ 217 | // ^ 218 | } 219 | } 220 | ``` 221 | 222 | #### Specifying id prefix for multi-db scenarios 223 | 224 | Use of idPrefix is recommended to ensure that table ids passed to ts-sql-query is unique when application can connect to tables with same name from multiple databases. 225 | 226 | ```ts 227 | const options = { 228 | tableMapping: { 229 | idPrefix: 'ReportingDB' 230 | } 231 | } 232 | ``` 233 | 234 | With this option the output looks like: 235 | 236 | ```ts 237 | export class AuthorsTable extends Table { 238 | // ~~~~~~~~~~~ 239 | // ^ 240 | // .. fields ... 241 | constructor() { 242 | super('authors') 243 | } 244 | } 245 | ``` 246 | 247 | This option will override the id prefix derived from schema name if `tableMapping.useQualifiedTableName` is true. 248 | 249 | #### Remove extraneous files 250 | 251 | By default, we don't delete any files. So, if you generate mappers for some tables, and then delete those tables from database, the corresponding mapper files will be left behind. 252 | 253 | If you pass `removeExtraneous: true` generator option, we will remove any extraneous files. If you enable this, you will need to ensure that the output directory is used only by ts-sql-codegen and you never add any additional files there yourself. 254 | 255 | ## Known Limitations 256 | 257 | 1. Only databases which are supported by both ts-sql-query and tbls can be supported. 258 | 1. While ts-sql-codegen works with many databases and adapters, this utility has been tested only with postgresql & sqlite. Please report bugs if you face issues with other databases. 259 | 1. Enum/custom type inspection support is currently limited - it is required to manually specify typescript types and adapters for now. 260 | 1. Typescript is assumed - plain js projects are not supported currently 261 | 262 | ## Contributing 263 | 264 | Thanks for your interest in contributing to this project. Pull requests and feature enhancements are welcome. 265 | 266 | This utility is being used in projects with many tables, so backward incompatible changes in generated code are highly undesirable. 267 | 268 | Feature flags are recommended for aspects which are not beneficial to all/most users. 269 | 270 | Code-generation should be last resort - if some feature can be supported in ts-sql-query itself, we recommending contributing there. 271 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ts-sql-codegen 2 | 3 | # ts-sql-codegen 4 | 5 | ## Table of contents 6 | 7 | ### Classes 8 | 9 | - [Generator](classes/Generator.md) 10 | 11 | ### Interfaces 12 | 13 | - [CommonCustomTypesOptions](interfaces/CommonCustomTypesOptions.md) 14 | - [CommonOptions](interfaces/CommonOptions.md) 15 | - [CommonPrimaryKeyOptions](interfaces/CommonPrimaryKeyOptions.md) 16 | - [CommonTypeAdapterOptions](interfaces/CommonTypeAdapterOptions.md) 17 | - [ExportOptions](interfaces/ExportOptions.md) 18 | - [FieldMapping](interfaces/FieldMapping.md) 19 | - [GeneratedField](interfaces/GeneratedField.md) 20 | - [GeneratedFieldType](interfaces/GeneratedFieldType.md) 21 | - [GeneratorOpts](interfaces/GeneratorOpts.md) 22 | - [ImportedItem](interfaces/ImportedItem.md) 23 | - [TableInclusion](interfaces/TableInclusion.md) 24 | 25 | ### Type Aliases 26 | 27 | - [StrOrRegExp](README.md#strorregexp) 28 | 29 | ### Variables 30 | 31 | - [fieldMappings](README.md#fieldmappings) 32 | 33 | ## Type Aliases 34 | 35 | ### StrOrRegExp 36 | 37 | Ƭ **StrOrRegExp**: `z.TypeOf`\ 38 | 39 | Matching criteria specified as string or regex 40 | 41 | #### Defined in 42 | 43 | [src/field-mappings.ts:8](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L8) 44 | 45 | ## Variables 46 | 47 | ### fieldMappings 48 | 49 | • `Const` **fieldMappings**: [`FieldMapping`](interfaces/FieldMapping.md)[] 50 | 51 | #### Defined in 52 | 53 | [src/field-mappings.ts:131](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L131) 54 | -------------------------------------------------------------------------------- /docs/interfaces/CommonCustomTypesOptions.md: -------------------------------------------------------------------------------- 1 | [ts-sql-codegen](../README.md) / CommonCustomTypesOptions 2 | 3 | # Interface: CommonCustomTypesOptions 4 | 5 | ## Hierarchy 6 | 7 | - `input`\ 8 | 9 | ↳ **`CommonCustomTypesOptions`** 10 | 11 | ## Table of contents 12 | 13 | ### Properties 14 | 15 | - [importPath](CommonCustomTypesOptions.md#importpath) 16 | 17 | ## Properties 18 | 19 | ### importPath 20 | 21 | • **importPath**: `string` 22 | 23 | Path from where custom types will be imported by default 24 | 25 | Relative to cwd 26 | 27 | #### Inherited from 28 | 29 | z.input.importPath 30 | 31 | #### Defined in 32 | 33 | [src/generator-options.ts:277](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L277) 34 | -------------------------------------------------------------------------------- /docs/interfaces/CommonOptions.md: -------------------------------------------------------------------------------- 1 | [ts-sql-codegen](../README.md) / CommonOptions 2 | 3 | # Interface: CommonOptions 4 | 5 | ## Hierarchy 6 | 7 | - `input`\ 8 | 9 | ↳ **`CommonOptions`** 10 | 11 | ## Table of contents 12 | 13 | ### Properties 14 | 15 | - [customTypes](CommonOptions.md#customtypes) 16 | - [primaryKey](CommonOptions.md#primarykey) 17 | - [typeAdapter](CommonOptions.md#typeadapter) 18 | 19 | ## Properties 20 | 21 | ### customTypes 22 | 23 | • `Optional` **customTypes**: ``null`` \| \{ `importPath`: `string` } 24 | 25 | **`See`** 26 | 27 | CommonCustomTypesOptions 28 | 29 | #### Inherited from 30 | 31 | z.input.customTypes 32 | 33 | #### Defined in 34 | 35 | [src/generator-options.ts:285](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L285) 36 | 37 | ___ 38 | 39 | ### primaryKey 40 | 41 | • `Optional` **primaryKey**: ``null`` \| \{ `isAutoGenerated?`: ``null`` \| `boolean` ; `name?`: ``null`` \| `string` } 42 | 43 | **`See`** 44 | 45 | CommonCustomTypesOptions 46 | 47 | #### Inherited from 48 | 49 | z.input.primaryKey 50 | 51 | #### Defined in 52 | 53 | [src/generator-options.ts:291](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L291) 54 | 55 | ___ 56 | 57 | ### typeAdapter 58 | 59 | • `Optional` **typeAdapter**: ``null`` \| \{ `importPath`: `string` } 60 | 61 | **`See`** 62 | 63 | CommonPrimaryKeyOptions 64 | 65 | #### Inherited from 66 | 67 | z.input.typeAdapter 68 | 69 | #### Defined in 70 | 71 | [src/generator-options.ts:288](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L288) 72 | -------------------------------------------------------------------------------- /docs/interfaces/CommonPrimaryKeyOptions.md: -------------------------------------------------------------------------------- 1 | [ts-sql-codegen](../README.md) / CommonPrimaryKeyOptions 2 | 3 | # Interface: CommonPrimaryKeyOptions 4 | 5 | ## Hierarchy 6 | 7 | - `input`\ 8 | 9 | ↳ **`CommonPrimaryKeyOptions`** 10 | 11 | ## Table of contents 12 | 13 | ### Properties 14 | 15 | - [isAutoGenerated](CommonPrimaryKeyOptions.md#isautogenerated) 16 | - [name](CommonPrimaryKeyOptions.md#name) 17 | 18 | ## Properties 19 | 20 | ### isAutoGenerated 21 | 22 | • `Optional` **isAutoGenerated**: ``null`` \| `boolean` 23 | 24 | If primary key column is auto-generated 25 | 26 | #### Inherited from 27 | 28 | z.input.isAutoGenerated 29 | 30 | #### Defined in 31 | 32 | [src/generator-options.ts:265](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L265) 33 | 34 | ___ 35 | 36 | ### name 37 | 38 | • `Optional` **name**: ``null`` \| `string` 39 | 40 | Name of primary key column 41 | 42 | #### Inherited from 43 | 44 | z.input.name 45 | 46 | #### Defined in 47 | 48 | [src/generator-options.ts:261](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L261) 49 | -------------------------------------------------------------------------------- /docs/interfaces/CommonTypeAdapterOptions.md: -------------------------------------------------------------------------------- 1 | [ts-sql-codegen](../README.md) / CommonTypeAdapterOptions 2 | 3 | # Interface: CommonTypeAdapterOptions 4 | 5 | ## Hierarchy 6 | 7 | - `input`\ 8 | 9 | ↳ **`CommonTypeAdapterOptions`** 10 | 11 | ## Table of contents 12 | 13 | ### Properties 14 | 15 | - [importPath](CommonTypeAdapterOptions.md#importpath) 16 | 17 | ## Properties 18 | 19 | ### importPath 20 | 21 | • **importPath**: `string` 22 | 23 | Common import path to be used for type adapters 24 | when no specific import path is specified at field level 25 | 26 | #### Inherited from 27 | 28 | z.input.importPath 29 | 30 | #### Defined in 31 | 32 | [src/generator-options.ts:237](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L237) 33 | -------------------------------------------------------------------------------- /docs/interfaces/ExportOptions.md: -------------------------------------------------------------------------------- 1 | [ts-sql-codegen](../README.md) / ExportOptions 2 | 3 | # Interface: ExportOptions 4 | 5 | ## Hierarchy 6 | 7 | - `input`\ 8 | 9 | ↳ **`ExportOptions`** 10 | 11 | ## Table of contents 12 | 13 | ### Properties 14 | 15 | - [columnTypeMappingInterface](ExportOptions.md#columntypemappinginterface) 16 | - [crudRepository](ExportOptions.md#crudrepository) 17 | - [extractedColumns](ExportOptions.md#extractedcolumns) 18 | - [rowTypes](ExportOptions.md#rowtypes) 19 | - [tableClasses](ExportOptions.md#tableclasses) 20 | - [tableInstances](ExportOptions.md#tableinstances) 21 | - [valuesTypes](ExportOptions.md#valuestypes) 22 | 23 | ## Properties 24 | 25 | ### columnTypeMappingInterface 26 | 27 | • `Optional` **columnTypeMappingInterface**: `boolean` 28 | 29 | Additionally export a column types mapping useful for constructing filter type 30 | for dynamic conditions. 31 | 32 | Example: 33 | export type UserCols = { 34 | id: 'int' 35 | name: 'string' 36 | } 37 | 38 | #### Inherited from 39 | 40 | z.input.columnTypeMappingInterface 41 | 42 | #### Defined in 43 | 44 | [src/generator-options.ts:113](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L113) 45 | 46 | ___ 47 | 48 | ### crudRepository 49 | 50 | • `Optional` **crudRepository**: `boolean` 51 | 52 | Generate a repository class to simplify common single-table CRUD operations 53 | 54 | This is currently only supported for tables having an id column as primary key 55 | 56 | #### Inherited from 57 | 58 | z.input.crudRepository 59 | 60 | #### Defined in 61 | 62 | [src/generator-options.ts:120](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L120) 63 | 64 | ___ 65 | 66 | ### extractedColumns 67 | 68 | • `Optional` **extractedColumns**: `boolean` 69 | 70 | Additionally export the extracted columns (Useful for select * queries etc.) 71 | 72 | Example: 73 | export const tUserCols = extractColumnsFrom(tUser) 74 | 75 | #### Inherited from 76 | 77 | z.input.extractedColumns 78 | 79 | #### Defined in 80 | 81 | [src/generator-options.ts:101](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L101) 82 | 83 | ___ 84 | 85 | ### rowTypes 86 | 87 | • `Optional` **rowTypes**: `boolean` \| \{ `asInterface`: `boolean` } 88 | 89 | Additionally export the row types associated with table 90 | 91 | Example: 92 | import { InsertableRow, UpdatableRow, SelectedRow } from "ts-sql-query/extras/types" 93 | 94 | export class UserTable extends Table { ... } 95 | 96 | // Type of user row that can be used for insert 97 | // Here computed columns will not be present and columns with defaults will be optional 98 | export type UserIRow = InsertableRow 99 | 100 | // Type of user row that can be used for update 101 | // Here computed columns will not be present and all fields will be optional 102 | export type UserURow = UpdatableRow 103 | 104 | // Type of user row that is returned from select 105 | // Here computed columns will be present, only nullable fields will be optional 106 | export type UserSRow = SelectedRow 107 | 108 | #### Inherited from 109 | 110 | z.input.rowTypes 111 | 112 | #### Defined in 113 | 114 | [src/generator-options.ts:70](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L70) 115 | 116 | ___ 117 | 118 | ### tableClasses 119 | 120 | • `Optional` **tableClasses**: `boolean` 121 | 122 | If set to false, prevents the table class from getting exported 123 | 124 | This is useful in conjunction with tableInstances, if you only want to 125 | export the table instance 126 | 127 | #### Inherited from 128 | 129 | z.input.tableClasses 130 | 131 | #### Defined in 132 | 133 | [src/generator-options.ts:47](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L47) 134 | 135 | ___ 136 | 137 | ### tableInstances 138 | 139 | • `Optional` **tableInstances**: `boolean` 140 | 141 | In addition to the table class, also expose instantiated instance of table class 142 | 143 | Example: 144 | export class UserTable extends Table { ... } 145 | 146 | export const tUserTable = new UserTable() // <---- 147 | 148 | #### Inherited from 149 | 150 | z.input.tableInstances 151 | 152 | #### Defined in 153 | 154 | [src/generator-options.ts:39](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L39) 155 | 156 | ___ 157 | 158 | ### valuesTypes 159 | 160 | • `Optional` **valuesTypes**: `boolean` \| \{ `asInterface`: `boolean` } 161 | 162 | Additionally export the value types associated with table 163 | 164 | Example: 165 | import { InsertableValues, UpdatableValues, SelectedValues } from "ts-sql-query/extras/types" 166 | 167 | export class UserTable extends Table { ... } 168 | 169 | // Type of user values that can be used for insert 170 | // Here computed columns will not be present and columns with defaults will be optional 171 | export type InsertableUser = InsertableValues 172 | 173 | // Type of user values that can be used for update 174 | // Here computed columns will not be present and all fields will be optional 175 | export type UpdatableUser = UpdatableValues 176 | 177 | // Type of user values that is returned from select 178 | // Here computed columns will be present, only nullable fields will be optional 179 | export type User = SelectedValues 180 | 181 | #### Inherited from 182 | 183 | z.input.valuesTypes 184 | 185 | #### Defined in 186 | 187 | [src/generator-options.ts:93](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L93) 188 | -------------------------------------------------------------------------------- /docs/interfaces/FieldMapping.md: -------------------------------------------------------------------------------- 1 | [ts-sql-codegen](../README.md) / FieldMapping 2 | 3 | # Interface: FieldMapping 4 | 5 | ## Hierarchy 6 | 7 | - `TypeOf`\ 8 | 9 | ↳ **`FieldMapping`** 10 | 11 | ## Table of contents 12 | 13 | ### Properties 14 | 15 | - [columnName](FieldMapping.md#columnname) 16 | - [columnType](FieldMapping.md#columntype) 17 | - [comment](FieldMapping.md#comment) 18 | - [generatedField](FieldMapping.md#generatedfield) 19 | - [tableName](FieldMapping.md#tablename) 20 | 21 | ## Properties 22 | 23 | ### columnName 24 | 25 | • `Optional` **columnName**: ``null`` \| `string` \| `RegExp` 26 | 27 | Optional criteria (string or regex) to match column name 28 | 29 | #### Inherited from 30 | 31 | z.TypeOf.columnName 32 | 33 | #### Defined in 34 | 35 | [src/field-mappings.ts:105](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L105) 36 | 37 | ___ 38 | 39 | ### columnType 40 | 41 | • `Optional` **columnType**: ``null`` \| `string` \| `RegExp` 42 | 43 | Optional criteria (string or regex) to match column type (in database) 44 | 45 | This will be used to match against the type name as 46 | present in the tbls output schema yaml file. 47 | 48 | #### Inherited from 49 | 50 | z.TypeOf.columnType 51 | 52 | #### Defined in 53 | 54 | [src/field-mappings.ts:116](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L116) 55 | 56 | ___ 57 | 58 | ### comment 59 | 60 | • `Optional` **comment**: ``null`` \| `string` 61 | 62 | #### Inherited from 63 | 64 | z.TypeOf.comment 65 | 66 | #### Defined in 67 | 68 | [src/field-mappings.ts:126](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L126) 69 | 70 | ___ 71 | 72 | ### generatedField 73 | 74 | • **generatedField**: ``false`` \| \{ `hasDefault?`: ``null`` \| `boolean` ; `isComputed?`: ``null`` \| `boolean` ; `isOptional?`: ``null`` \| `boolean` ; `name?`: ``null`` \| `string` ; `type?`: ``null`` \| \{ `adapter?`: ``null`` \| \{ `importPath?`: ``null`` \| `string` ; `isDefault?`: ``null`` \| `boolean` ; `isRelative?`: ``null`` \| `boolean` ; `name`: `string` } ; `dbType?`: ``null`` \| \{ `name`: `string` } ; `kind?`: ``null`` \| ``"custom"`` \| ``"customComparable"`` \| ``"enum"`` \| ``"customInt"`` \| ``"customDouble"`` \| ``"customUuid"`` \| ``"customLocalDate"`` \| ``"customLocalTime"`` \| ``"customLocalDateTime"`` ; `tsType?`: ``null`` \| \{ `importPath?`: ``null`` \| `string` ; `isDefault?`: ``null`` \| `boolean` ; `isRelative?`: ``null`` \| `boolean` ; `name`: `string` } } } 75 | 76 | Can be used to customize the field name or type mapping 77 | in the generated field. 78 | 79 | Set to false to omit mapping of this field 80 | 81 | #### Inherited from 82 | 83 | z.TypeOf.generatedField 84 | 85 | #### Defined in 86 | 87 | [src/field-mappings.ts:124](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L124) 88 | 89 | ___ 90 | 91 | ### tableName 92 | 93 | • `Optional` **tableName**: ``null`` \| `string` \| `RegExp` 94 | 95 | Optional criteria (string or regex) to match table name 96 | 97 | #### Inherited from 98 | 99 | z.TypeOf.tableName 100 | 101 | #### Defined in 102 | 103 | [src/field-mappings.ts:108](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L108) 104 | -------------------------------------------------------------------------------- /docs/interfaces/GeneratedField.md: -------------------------------------------------------------------------------- 1 | [ts-sql-codegen](../README.md) / GeneratedField 2 | 3 | # Interface: GeneratedField 4 | 5 | Specifies options for the generated field mapping 6 | 7 | The options here are ts-sql-query specific 8 | 9 | ## Hierarchy 10 | 11 | - `TypeOf`\ 12 | 13 | ↳ **`GeneratedField`** 14 | 15 | ## Table of contents 16 | 17 | ### Properties 18 | 19 | - [hasDefault](GeneratedField.md#hasdefault) 20 | - [isComputed](GeneratedField.md#iscomputed) 21 | - [isOptional](GeneratedField.md#isoptional) 22 | - [name](GeneratedField.md#name) 23 | - [type](GeneratedField.md#type) 24 | 25 | ## Properties 26 | 27 | ### hasDefault 28 | 29 | • `Optional` **hasDefault**: ``null`` \| `boolean` 30 | 31 | #### Inherited from 32 | 33 | z.TypeOf.hasDefault 34 | 35 | #### Defined in 36 | 37 | [src/field-mappings.ts:93](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L93) 38 | 39 | ___ 40 | 41 | ### isComputed 42 | 43 | • `Optional` **isComputed**: ``null`` \| `boolean` 44 | 45 | #### Inherited from 46 | 47 | z.TypeOf.isComputed 48 | 49 | #### Defined in 50 | 51 | [src/field-mappings.ts:91](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L91) 52 | 53 | ___ 54 | 55 | ### isOptional 56 | 57 | • `Optional` **isOptional**: ``null`` \| `boolean` 58 | 59 | #### Inherited from 60 | 61 | z.TypeOf.isOptional 62 | 63 | #### Defined in 64 | 65 | [src/field-mappings.ts:92](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L92) 66 | 67 | ___ 68 | 69 | ### name 70 | 71 | • `Optional` **name**: ``null`` \| `string` 72 | 73 | #### Inherited from 74 | 75 | z.TypeOf.name 76 | 77 | #### Defined in 78 | 79 | [src/field-mappings.ts:90](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L90) 80 | 81 | ___ 82 | 83 | ### type 84 | 85 | • `Optional` **type**: ``null`` \| \{ `adapter?`: ``null`` \| \{ `importPath?`: ``null`` \| `string` ; `isDefault?`: ``null`` \| `boolean` ; `isRelative?`: ``null`` \| `boolean` ; `name`: `string` } ; `dbType?`: ``null`` \| \{ `name`: `string` } ; `kind?`: ``null`` \| ``"custom"`` \| ``"customComparable"`` \| ``"enum"`` \| ``"customInt"`` \| ``"customDouble"`` \| ``"customUuid"`` \| ``"customLocalDate"`` \| ``"customLocalTime"`` \| ``"customLocalDateTime"`` ; `tsType?`: ``null`` \| \{ `importPath?`: ``null`` \| `string` ; `isDefault?`: ``null`` \| `boolean` ; `isRelative?`: ``null`` \| `boolean` ; `name`: `string` } } 86 | 87 | #### Inherited from 88 | 89 | z.TypeOf.type 90 | 91 | #### Defined in 92 | 93 | [src/field-mappings.ts:89](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L89) 94 | -------------------------------------------------------------------------------- /docs/interfaces/GeneratedFieldType.md: -------------------------------------------------------------------------------- 1 | [ts-sql-codegen](../README.md) / GeneratedFieldType 2 | 3 | # Interface: GeneratedFieldType 4 | 5 | ## Hierarchy 6 | 7 | - `TypeOf`\ 8 | 9 | ↳ **`GeneratedFieldType`** 10 | 11 | ## Table of contents 12 | 13 | ### Properties 14 | 15 | - [adapter](GeneratedFieldType.md#adapter) 16 | - [dbType](GeneratedFieldType.md#dbtype) 17 | - [kind](GeneratedFieldType.md#kind) 18 | - [tsType](GeneratedFieldType.md#tstype) 19 | 20 | ## Properties 21 | 22 | ### adapter 23 | 24 | • `Optional` **adapter**: ``null`` \| \{ `importPath?`: ``null`` \| `string` ; `isDefault?`: ``null`` \| `boolean` ; `isRelative?`: ``null`` \| `boolean` ; `name`: `string` } 25 | 26 | Specify a type adapter for the generated field. 27 | 28 | If not present, we will attempt to use GeneratorOpts.common.typeAdapter.importPath or throw if absent. 29 | 30 | #### Inherited from 31 | 32 | z.TypeOf.adapter 33 | 34 | #### Defined in 35 | 36 | [src/field-mappings.ts:82](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L82) 37 | 38 | ___ 39 | 40 | ### dbType 41 | 42 | • `Optional` **dbType**: ``null`` \| \{ `name`: `string` } 43 | 44 | This name is a database type identifier as expected by ts-sql-query 45 | 46 | These names are not database specific and may not match FieldMappingSchema.columnType eg. for database level type (which tbls outputs in the schema yaml) can be varchar but the columnType that ts-sql-query uses will be 'string' 47 | 48 | #### Inherited from 49 | 50 | z.TypeOf.dbType 51 | 52 | #### Defined in 53 | 54 | [src/field-mappings.ts:66](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L66) 55 | 56 | ___ 57 | 58 | ### kind 59 | 60 | • `Optional` **kind**: ``null`` \| ``"custom"`` \| ``"customComparable"`` \| ``"enum"`` \| ``"customInt"`` \| ``"customDouble"`` \| ``"customUuid"`` \| ``"customLocalDate"`` \| ``"customLocalTime"`` \| ``"customLocalDateTime"`` 61 | 62 | Specify that this field uses a custom database type or an enum type 63 | 64 | #### Inherited from 65 | 66 | z.TypeOf.kind 67 | 68 | #### Defined in 69 | 70 | [src/field-mappings.ts:60](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L60) 71 | 72 | ___ 73 | 74 | ### tsType 75 | 76 | • `Optional` **tsType**: ``null`` \| \{ `importPath?`: ``null`` \| `string` ; `isDefault?`: ``null`` \| `boolean` ; `isRelative?`: ``null`` \| `boolean` ; `name`: `string` } 77 | 78 | If present, used as a generic type argument to field factory. This is typically useful when 79 | dealing with custom database types or enum types. 80 | 81 | If importPath is not present, then an import will not be added. This can result in a compile time error 82 | if the type is not globally available. 83 | 84 | #### Inherited from 85 | 86 | z.TypeOf.tsType 87 | 88 | #### Defined in 89 | 90 | [src/field-mappings.ts:75](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L75) 91 | -------------------------------------------------------------------------------- /docs/interfaces/GeneratorOpts.md: -------------------------------------------------------------------------------- 1 | [ts-sql-codegen](../README.md) / GeneratorOpts 2 | 3 | # Interface: GeneratorOpts 4 | 5 | Generator options 6 | 7 | ## Hierarchy 8 | 9 | - `input`\ 10 | 11 | ↳ **`GeneratorOpts`** 12 | 13 | ## Table of contents 14 | 15 | ### Properties 16 | 17 | - [common](GeneratorOpts.md#common) 18 | - [connectionSource](GeneratorOpts.md#connectionsource) 19 | - [connectionSourcePath](GeneratorOpts.md#connectionsourcepath) 20 | - [dryRun](GeneratorOpts.md#dryrun) 21 | - [export](GeneratorOpts.md#export) 22 | - [fieldMappings](GeneratorOpts.md#fieldmappings) 23 | - [includeDBTypeWhenIsOptional](GeneratorOpts.md#includedbtypewhenisoptional) 24 | - [moduleRoot](GeneratorOpts.md#moduleroot) 25 | - [naming](GeneratorOpts.md#naming) 26 | - [output](GeneratorOpts.md#output) 27 | - [outputDirPath](GeneratorOpts.md#outputdirpath) 28 | - [rawContent](GeneratorOpts.md#rawcontent) 29 | - [removeExtraneous](GeneratorOpts.md#removeextraneous) 30 | - [schemaPath](GeneratorOpts.md#schemapath) 31 | - [tableMapping](GeneratorOpts.md#tablemapping) 32 | - [tables](GeneratorOpts.md#tables) 33 | - [typeWrappers](GeneratorOpts.md#typewrappers) 34 | 35 | ## Properties 36 | 37 | ### common 38 | 39 | • `Optional` **common**: ``null`` \| \{ `customTypes?`: ``null`` \| \{ `importPath`: `string` } ; `primaryKey?`: ``null`` \| \{ `isAutoGenerated?`: ``null`` \| `boolean` ; `name?`: ``null`` \| `string` } ; `typeAdapter?`: ``null`` \| \{ `importPath`: `string` } } 40 | 41 | Convenience utility for common cases where all tables 42 | follow same conventions 43 | 44 | See [CommonOptions](CommonOptions.md) 45 | 46 | #### Inherited from 47 | 48 | z.input.common 49 | 50 | #### Defined in 51 | 52 | [src/generator-options.ts:401](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L401) 53 | 54 | ___ 55 | 56 | ### connectionSource 57 | 58 | • `Optional` **connectionSource**: ``null`` \| \{ `path?`: ``null`` \| `string` ; `resolveRelative?`: ``null`` \| `boolean` } 59 | 60 | Connection source configuration 61 | 62 | **`See`** 63 | 64 | ConnectionSourceOptions 65 | 66 | #### Inherited from 67 | 68 | z.input.connectionSource 69 | 70 | #### Defined in 71 | 72 | [src/generator-options.ts:353](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L353) 73 | 74 | ___ 75 | 76 | ### connectionSourcePath 77 | 78 | • `Optional` **connectionSourcePath**: ``null`` \| `string` 79 | 80 | Path to module that exports DBConnection object used in table mappers 81 | 82 | **`Deprecated`** 83 | 84 | **`See`** 85 | 86 | connectionSource 87 | 88 | #### Inherited from 89 | 90 | z.input.connectionSourcePath 91 | 92 | #### Defined in 93 | 94 | [src/generator-options.ts:344](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L344) 95 | 96 | ___ 97 | 98 | ### dryRun 99 | 100 | • `Optional` **dryRun**: ``null`` \| `boolean` 101 | 102 | Simulate the generation and print the outcome without actually modifying any files 103 | 104 | #### Inherited from 105 | 106 | z.input.dryRun 107 | 108 | #### Defined in 109 | 110 | [src/generator-options.ts:331](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L331) 111 | 112 | ___ 113 | 114 | ### export 115 | 116 | • `Optional` **export**: ``null`` \| \{ `columnTypeMappingInterface?`: `boolean` ; `crudRepository?`: `boolean` ; `extractedColumns?`: `boolean` ; `rowTypes?`: `boolean` \| \{ `asInterface`: `boolean` } ; `tableClasses?`: `boolean` ; `tableInstances?`: `boolean` ; `valuesTypes?`: `boolean` \| \{ `asInterface`: `boolean` } } 117 | 118 | Customize what all entities are exported from generated file 119 | 120 | **`See`** 121 | 122 | ExportOptions 123 | 124 | #### Inherited from 125 | 126 | z.input.export 127 | 128 | #### Defined in 129 | 130 | [src/generator-options.ts:393](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L393) 131 | 132 | ___ 133 | 134 | ### fieldMappings 135 | 136 | • `Optional` **fieldMappings**: ``null`` \| \{ `columnName?`: ``null`` \| `string` \| `RegExp` ; `columnType?`: ``null`` \| `string` \| `RegExp` ; `comment?`: ``null`` \| `string` ; `generatedField`: ``false`` \| \{ `hasDefault?`: ``null`` \| `boolean` ; `isComputed?`: ``null`` \| `boolean` ; `isOptional?`: ``null`` \| `boolean` ; `name?`: ``null`` \| `string` ; `type?`: ``null`` \| \{ `adapter?`: ``null`` \| \{ `importPath?`: ``null`` \| `string` ; `isDefault?`: ``null`` \| `boolean` ; `isRelative?`: ``null`` \| `boolean` ; `name`: `string` } ; `dbType?`: ``null`` \| \{ `name`: `string` } ; `kind?`: ``null`` \| ``"custom"`` \| ``"customComparable"`` \| ``"enum"`` \| ``"customInt"`` \| ``"customDouble"`` \| ``"customUuid"`` \| ``"customLocalDate"`` \| ``"customLocalTime"`` \| ``"customLocalDateTime"`` ; `tsType?`: ``null`` \| \{ `importPath?`: ``null`` \| `string` ; `isDefault?`: ``null`` \| `boolean` ; `isRelative?`: ``null`` \| `boolean` ; `name`: `string` } } } ; `tableName?`: ``null`` \| `string` \| `RegExp` }[] 137 | 138 | Customize how table columns are mapped to typescript fields 139 | 140 | **`See`** 141 | 142 | FieldMapping 143 | 144 | #### Inherited from 145 | 146 | z.input.fieldMappings 147 | 148 | #### Defined in 149 | 150 | [src/generator-options.ts:366](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L366) 151 | 152 | ___ 153 | 154 | ### includeDBTypeWhenIsOptional 155 | 156 | • `Optional` **includeDBTypeWhenIsOptional**: ``null`` \| `boolean` 157 | 158 | The fields marked as "custom", "customComparable" or "enum" receive a second generic 159 | argument that need to be the same of the db type in the database or redefined for the field 160 | If you set to true this property that second generic argument will be generated. 161 | 162 | #### Inherited from 163 | 164 | z.input.includeDBTypeWhenIsOptional 165 | 166 | #### Defined in 167 | 168 | [src/generator-options.ts:415](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L415) 169 | 170 | ___ 171 | 172 | ### moduleRoot 173 | 174 | • `Optional` **moduleRoot**: ``null`` \| `string` 175 | 176 | Root path of module - used for resolving relative paths. If unspecified, assumed to be cwd 177 | 178 | #### Inherited from 179 | 180 | z.input.moduleRoot 181 | 182 | #### Defined in 183 | 184 | [src/generator-options.ts:328](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L328) 185 | 186 | ___ 187 | 188 | ### naming 189 | 190 | • `Optional` **naming**: ``null`` \| \{ `columnTypeMappingInterfaceNameSuffix?`: `string` ; `crudRepositoryClassNamePrefix?`: `string` ; `crudRepositoryClassNameSuffix?`: `string` ; `insertableRowTypeNamePrefix?`: `string` ; `insertableRowTypeNameSuffix?`: `string` ; `insertableValuesTypeNamePrefix?`: `string` ; `insertableValuesTypeNameSuffix?`: `string` ; `selectedRowTypeNamePrefix?`: `string` ; `selectedRowTypeNameSuffix?`: `string` ; `selectedValuesTypeNamePrefix?`: `string` ; `selectedValuesTypeNameSuffix?`: `string` ; `tableClassNamePrefix?`: `string` ; `tableClassNameSuffix?`: `string` ; `tableColumnsNamePrefix?`: `string` ; `tableColumnsNameSuffix?`: `string` ; `tableInstanceNamePrefix?`: `string` ; `tableInstanceNameSuffix?`: `string` ; `updatableRowTypeNamePrefix?`: `string` ; `updatableRowTypeNameSuffix?`: `string` ; `updatableValuesTypeNamePrefix?`: `string` ; `updatableValuesTypeNameSuffix?`: `string` ; `viewClassNamePrefix?`: `string` ; `viewClassNameSuffix?`: `string` ; `viewColumnsNamePrefix?`: `string` ; `viewColumnsNameSuffix?`: `string` ; `viewInstanceNamePrefix?`: `string` ; `viewInstanceNameSuffix?`: `string` } 191 | 192 | Customize the naming rules of the generated items 193 | 194 | See NamingOptions 195 | 196 | #### Inherited from 197 | 198 | z.input.naming 199 | 200 | #### Defined in 201 | 202 | [src/generator-options.ts:408](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L408) 203 | 204 | ___ 205 | 206 | ### output 207 | 208 | • `Optional` **output**: ``null`` \| \{ `import?`: ``null`` \| \{ `extension?`: ``null`` \| `string` } } 209 | 210 | Shared options that affect all generated output 211 | 212 | #### Inherited from 213 | 214 | z.input.output 215 | 216 | #### Defined in 217 | 218 | [src/generator-options.ts:386](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L386) 219 | 220 | ___ 221 | 222 | ### outputDirPath 223 | 224 | • `Optional` **outputDirPath**: ``null`` \| `string` 225 | 226 | Path to output directory where a typescript class file will be generated for each table 227 | 228 | #### Inherited from 229 | 230 | z.input.outputDirPath 231 | 232 | #### Defined in 233 | 234 | [src/generator-options.ts:356](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L356) 235 | 236 | ___ 237 | 238 | ### rawContent 239 | 240 | • `Optional` **rawContent**: ``null`` \| \{ `after?`: ``null`` \| `string` ; `before?`: ``null`` \| `string` } 241 | 242 | Support injection of raw content in the generated files. 243 | This is useful for adding things like eslint-disable, additional exports etc. 244 | 245 | **`See`** 246 | 247 | RawContent 248 | 249 | #### Inherited from 250 | 251 | z.input.rawContent 252 | 253 | #### Defined in 254 | 255 | [src/generator-options.ts:437](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L437) 256 | 257 | ___ 258 | 259 | ### removeExtraneous 260 | 261 | • `Optional` **removeExtraneous**: ``null`` \| ``"never"`` \| ``"interactively"`` \| ``"all"`` 262 | 263 | Remove extraneous files after code generation completes - this prevents you from 264 | having to manually clean up files after eg. any table has been deleted, but it is 265 | your responsibility to ensure that the outputDir used solely for files generated through 266 | this utility and all files are written as part of single run. 267 | 268 | Defauls to retaining all extraneous files. 269 | 270 | #### Inherited from 271 | 272 | z.input.removeExtraneous 273 | 274 | #### Defined in 275 | 276 | [src/generator-options.ts:425](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L425) 277 | 278 | ___ 279 | 280 | ### schemaPath 281 | 282 | • `Optional` **schemaPath**: ``null`` \| `string` 283 | 284 | Path to yaml schema dumped by tbls 285 | 286 | #### Inherited from 287 | 288 | z.input.schemaPath 289 | 290 | #### Defined in 291 | 292 | [src/generator-options.ts:334](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L334) 293 | 294 | ___ 295 | 296 | ### tableMapping 297 | 298 | • `Optional` **tableMapping**: ``null`` \| \{ `idPrefix?`: ``null`` \| `string` ; `useQualifiedTableName?`: ``null`` \| `boolean` } 299 | 300 | Customize how tables are mapped 301 | 302 | **`See`** 303 | 304 | TableMapping 305 | 306 | #### Inherited from 307 | 308 | z.input.tableMapping 309 | 310 | #### Defined in 311 | 312 | [src/generator-options.ts:373](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L373) 313 | 314 | ___ 315 | 316 | ### tables 317 | 318 | • `Optional` **tables**: ``null`` \| \{ `exclude?`: ``null`` \| (`string` \| `RegExp`)[] ; `include?`: ``null`` \| (`string` \| `RegExp`)[] } 319 | 320 | Restrict the generator to process only a subset of tables 321 | available 322 | 323 | **`See`** 324 | 325 | TableInclusion 326 | 327 | #### Inherited from 328 | 329 | z.input.tables 330 | 331 | #### Defined in 332 | 333 | [src/generator-options.ts:381](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L381) 334 | 335 | ___ 336 | 337 | ### typeWrappers 338 | 339 | • `Optional` **typeWrappers**: ``null`` \| \{ `typeName`: `string` \| `RegExp` = StrOrRegExpSchema; `wrapper`: \{ `importPath?`: ``null`` \| `string` ; `isDefault?`: ``null`` \| `boolean` ; `isRelative?`: ``null`` \| `boolean` ; `name`: `string` } = ImportedItemSchema }[] 340 | 341 | Wrap inferred types before exporting - this is useful to restrict 342 | the types used for insert/update etc. beyond what the database permits. 343 | 344 | Eg. We can hint that updatedAt must be set whenever record is updated 345 | 346 | **`See`** 347 | 348 | TypeWapper 349 | 350 | #### Inherited from 351 | 352 | z.input.typeWrappers 353 | 354 | #### Defined in 355 | 356 | [src/generator-options.ts:447](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L447) 357 | -------------------------------------------------------------------------------- /docs/interfaces/ImportedItem.md: -------------------------------------------------------------------------------- 1 | [ts-sql-codegen](../README.md) / ImportedItem 2 | 3 | # Interface: ImportedItem 4 | 5 | Specifies options to construct an import 6 | 7 | Note that unless isRelative is specified as false, the import will be 8 | resolved relative to the cwd from where generator is invoked and 9 | then converted to a relative path relative to the generated file 10 | 11 | Examples: 12 | When generated file is located at src/db/tables/some-table.ts and generator 13 | is run from project root 14 | 15 | Config: `{ name: "FooAdapter", importPath: 'src/db/adapters/foo-adapter'}` 16 | Generates: `import { FooAdapter } from '../adapters/foo-adapter'` 17 | 18 | Config: `{ name: "FooAdapter", isDefault: true, importPath: 'src/db/adapters/foo-adapter'}` 19 | Generates: `import FooAdapter from '../adapters/foo-adapter'` 20 | 21 | Config: `{ name: "FooAdapter", isRelative: false, importPath: 'external-lib/foo-adapter'}` 22 | Generates: `import { FooAdapter } from '../adapters';` 23 | 24 | ## Hierarchy 25 | 26 | - `TypeOf`\ 27 | 28 | ↳ **`ImportedItem`** 29 | 30 | ## Table of contents 31 | 32 | ### Properties 33 | 34 | - [importPath](ImportedItem.md#importpath) 35 | - [isDefault](ImportedItem.md#isdefault) 36 | - [isRelative](ImportedItem.md#isrelative) 37 | - [name](ImportedItem.md#name) 38 | 39 | ## Properties 40 | 41 | ### importPath 42 | 43 | • `Optional` **importPath**: ``null`` \| `string` 44 | 45 | Path from which we should import 46 | 47 | #### Inherited from 48 | 49 | z.TypeOf.importPath 50 | 51 | #### Defined in 52 | 53 | [src/field-mappings.ts:16](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L16) 54 | 55 | ___ 56 | 57 | ### isDefault 58 | 59 | • `Optional` **isDefault**: ``null`` \| `boolean` 60 | 61 | Whether this is a default import 62 | 63 | **`Default`** 64 | 65 | ```ts 66 | false 67 | ``` 68 | 69 | #### Inherited from 70 | 71 | z.TypeOf.isDefault 72 | 73 | #### Defined in 74 | 75 | [src/field-mappings.ts:23](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L23) 76 | 77 | ___ 78 | 79 | ### isRelative 80 | 81 | • `Optional` **isRelative**: ``null`` \| `boolean` 82 | 83 | Whether this is a relative import 84 | 85 | **`Default`** 86 | 87 | ```ts 88 | true 89 | ``` 90 | 91 | #### Inherited from 92 | 93 | z.TypeOf.isRelative 94 | 95 | #### Defined in 96 | 97 | [src/field-mappings.ts:30](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L30) 98 | 99 | ___ 100 | 101 | ### name 102 | 103 | • **name**: `string` 104 | 105 | Name of import 106 | 107 | #### Inherited from 108 | 109 | z.TypeOf.name 110 | 111 | #### Defined in 112 | 113 | [src/field-mappings.ts:13](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/field-mappings.ts#L13) 114 | -------------------------------------------------------------------------------- /docs/interfaces/TableInclusion.md: -------------------------------------------------------------------------------- 1 | [ts-sql-codegen](../README.md) / TableInclusion 2 | 3 | # Interface: TableInclusion 4 | 5 | ## Hierarchy 6 | 7 | - `input`\ 8 | 9 | ↳ **`TableInclusion`** 10 | 11 | ## Table of contents 12 | 13 | ### Properties 14 | 15 | - [exclude](TableInclusion.md#exclude) 16 | - [include](TableInclusion.md#include) 17 | 18 | ## Properties 19 | 20 | ### exclude 21 | 22 | • `Optional` **exclude**: ``null`` \| (`string` \| `RegExp`)[] 23 | 24 | Tables to be excluded - identified by qualified table name 25 | or regular expression 26 | 27 | #### Inherited from 28 | 29 | z.input.exclude 30 | 31 | #### Defined in 32 | 33 | [src/generator-options.ts:14](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L14) 34 | 35 | ___ 36 | 37 | ### include 38 | 39 | • `Optional` **include**: ``null`` \| (`string` \| `RegExp`)[] 40 | 41 | Tables to be included - identified by qualified table name 42 | or regular expression 43 | 44 | #### Inherited from 45 | 46 | z.input.include 47 | 48 | #### Defined in 49 | 50 | [src/generator-options.ts:9](https://github.com/lorefnon/ts-sql-codegen/blob/7fbf2a8eefc564235a09365113d5ea88b70cfc39/src/generator-options.ts#L9) 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-sql-codegen", 3 | "version": "3.23.1", 4 | "description": "Generates ts-sql-query table mappings from tbls schema output", 5 | "main": "dist/index.js", 6 | "bin": "dist/cli.js", 7 | "types": "dist/index.d.ts", 8 | "scripts": { 9 | "prepublishOnly": "pnpm run build", 10 | "build": "pnpm run build:ts && pnpm run copy:templates && pnpm run build:docs", 11 | "build:ts": "tsc", 12 | "build:docs": "typedoc --plugin typedoc-plugin-markdown --readme none src/index.ts", 13 | "copy:templates": "recursive-copy -w src dist -f \"*.hbs\"", 14 | "clean": "rimraf dist test/generated", 15 | "test": "mocha -r ts-node/register -r mocha-snap --extensions ts,js \"./test/**/*.ts\"" 16 | }, 17 | "files": [ 18 | "dist/**/*" 19 | ], 20 | "keywords": [ 21 | "sql", 22 | "code-generator", 23 | "ts-sql-query", 24 | "query-builder", 25 | "typescript" 26 | ], 27 | "author": { 28 | "name": "Lorefnon", 29 | "email": "lorefnon@tutanota.com", 30 | "url": "https://lorefnon.me" 31 | }, 32 | "repository": "github:lorefnon/ts-sql-codegen", 33 | "license": "MIT", 34 | "devDependencies": { 35 | "@types/glob": "^8.1.0", 36 | "@types/js-yaml": "^4.0.9", 37 | "@types/lodash": "^4.14.202", 38 | "@types/mocha": "^10.0.6", 39 | "@types/node": "^20.11.19", 40 | "@types/pg": "^8.11.0", 41 | "@types/prompts": "^2.4.9", 42 | "@types/rimraf": "^3.0.2", 43 | "better-sqlite3": "^9.4.1", 44 | "mocha": "^10.3.0", 45 | "mocha-snap": "^5.0.0", 46 | "pg": "^8.11.3", 47 | "recursive-copy-cli": "^1.0.20", 48 | "rimraf": "^5.0.5", 49 | "ts-node": "^10.9.2", 50 | "ts-sql-query": "^1.59.0", 51 | "typedoc": "^0.25.8", 52 | "typedoc-plugin-markdown": "^3.17.1", 53 | "typescript": "^5.3.3" 54 | }, 55 | "peerDependencies": { 56 | "ts-sql-query": "^1.29.0" 57 | }, 58 | "dependencies": { 59 | "glob": "^10.3.10", 60 | "handlebars": "^4.7.8", 61 | "hbs-dedent-helper": "^0.3.0", 62 | "js-yaml": "^4.1.0", 63 | "lodash": "^4.17.21", 64 | "prompts": "^2.4.2", 65 | "ts-pattern": "^5.0.8", 66 | "zod": "^3.22.4" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { parseArgs } from "util"; 4 | import { ZodError } from "zod"; 5 | import { Generator } from "./generator"; 6 | import fs from "fs/promises"; 7 | import path from "path"; 8 | import Handlebars from "handlebars"; 9 | import { GeneratorOpts } from "./generator-options"; 10 | 11 | main().catch((e) => { 12 | console.error(e); 13 | if (e instanceof ZodError) { 14 | printHelp(); 15 | } 16 | process.exit(1); 17 | }); 18 | 19 | async function main() { 20 | const { values: argv } = parseArgs({ 21 | options: { 22 | 'help': { type: 'boolean', short: 'h' }, 23 | 'version': { type: 'boolean', short: 'v' }, 24 | 'dry-run': { type: 'boolean' }, 25 | 'schema': { type: 'string', short: 's' }, 26 | 'connection-source': { type: 'string', short: 'c' }, 27 | 'output-dir': { type: 'string', short: 'o' }, 28 | 'output-import-ext': { type: 'string' }, 29 | 'remove-extraneous': { type: 'string' }, 30 | 'export-table-instances': { type: 'boolean' }, 31 | 'export-row-types': { type: 'boolean' }, 32 | 'export-table-classes': { type: 'boolean' }, 33 | 'export-values-types': { type: 'boolean' }, 34 | 'export-extracted-columns': { type: 'boolean' } 35 | } 36 | }); 37 | if (argv.help) { 38 | await printHelp(); 39 | return; 40 | } 41 | if (argv.version) { 42 | await printVersion(); 43 | return; 44 | } 45 | const generator = new Generator({ 46 | dryRun: argv["dry-run"], 47 | schemaPath: argv["schema"], 48 | connectionSourcePath: argv["connection-source"], 49 | outputDirPath: argv["output-dir"], 50 | removeExtraneous: argv["remove-extraneous"] ?? "never", 51 | output: { 52 | import: { 53 | extension: argv["output-import-ext"], 54 | } 55 | }, 56 | export: { 57 | tableInstances: argv['export-table-instances'], 58 | tableClasses: argv['export-table-classes'], 59 | rowTypes: argv['export-row-types'], 60 | valuesTypes: argv['export-values-types'], 61 | extractedColumns: argv['export-extracted-columns'] 62 | } 63 | } as GeneratorOpts); 64 | await generator.generate(); 65 | } 66 | 67 | async function printVersion() { 68 | const manifest = await readManifest(); 69 | console.log(`${manifest.name} version ${manifest.version}`); 70 | } 71 | 72 | async function printHelp() { 73 | const helpTemplate = await compileHelpTemplate(); 74 | const templateInput = await readManifest(); 75 | console.log(helpTemplate(templateInput)); 76 | } 77 | 78 | async function readManifest(): Promise { 79 | const rawManifest = await fs.readFile( 80 | path.join(__dirname, "../package.json"), 81 | "utf8" 82 | ); 83 | return JSON.parse(rawManifest); 84 | } 85 | 86 | async function compileHelpTemplate () { 87 | const templatePath = path.join(__dirname, "help.hbs"); 88 | const templateContent = await fs.readFile(templatePath, 'utf8'); 89 | return Handlebars.compile(templateContent); 90 | } 91 | -------------------------------------------------------------------------------- /src/field-mappings.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | 3 | export const StrOrRegExpSchema = z.string().or(z.instanceof(RegExp)); 4 | 5 | /** 6 | * Matching criteria specified as string or regex 7 | */ 8 | export type StrOrRegExp = z.TypeOf; 9 | 10 | export const ImportedItemSchema = z.object({ 11 | 12 | /** Name of import */ 13 | name: z.string(), 14 | 15 | /** Path from which we should import */ 16 | importPath: z.string().nullish(), 17 | 18 | /** 19 | * Whether this is a default import 20 | * 21 | * @default false 22 | */ 23 | isDefault: z.boolean().nullish(), 24 | 25 | /** 26 | * Whether this is a relative import 27 | * 28 | * @default true 29 | */ 30 | isRelative: z.boolean().nullish() 31 | }); 32 | 33 | /** 34 | * Specifies options to construct an import 35 | * 36 | * Note that unless isRelative is specified as false, the import will be 37 | * resolved relative to the cwd from where generator is invoked and 38 | * then converted to a relative path relative to the generated file 39 | * 40 | * Examples: 41 | * When generated file is located at src/db/tables/some-table.ts and generator 42 | * is run from project root 43 | * 44 | * Config: `{ name: "FooAdapter", importPath: 'src/db/adapters/foo-adapter'}` 45 | * Generates: `import { FooAdapter } from '../adapters/foo-adapter'` 46 | * 47 | * Config: `{ name: "FooAdapter", isDefault: true, importPath: 'src/db/adapters/foo-adapter'}` 48 | * Generates: `import FooAdapter from '../adapters/foo-adapter'` 49 | * 50 | * Config: `{ name: "FooAdapter", isRelative: false, importPath: 'external-lib/foo-adapter'}` 51 | * Generates: `import { FooAdapter } from '../adapters';` 52 | * 53 | */ 54 | export interface ImportedItem extends z.TypeOf {} 55 | 56 | export const GeneratedFieldTypeSchema = z.object({ 57 | /** 58 | * Specify that this field uses a custom database type or an enum type 59 | */ 60 | kind: z.enum(["custom", "customComparable", "enum", "customInt", "customDouble", "customUuid", "customLocalDate", "customLocalTime", "customLocalDateTime"]).nullish(), 61 | /** 62 | * This name is a database type identifier as expected by ts-sql-query 63 | * 64 | * These names are not database specific and may not match FieldMappingSchema.columnType eg. for database level type (which tbls outputs in the schema yaml) can be varchar but the columnType that ts-sql-query uses will be 'string' 65 | */ 66 | dbType: z.object({ name: z.string() }).nullish(), 67 | 68 | /** 69 | * If present, used as a generic type argument to field factory. This is typically useful when 70 | * dealing with custom database types or enum types. 71 | * 72 | * If importPath is not present, then an import will not be added. This can result in a compile time error 73 | * if the type is not globally available. 74 | */ 75 | tsType: ImportedItemSchema.nullish(), 76 | 77 | /** 78 | * Specify a type adapter for the generated field. 79 | * 80 | * If not present, we will attempt to use GeneratorOpts.common.typeAdapter.importPath or throw if absent. 81 | */ 82 | adapter: ImportedItemSchema.nullish(), 83 | }); 84 | 85 | export interface GeneratedFieldType 86 | extends z.TypeOf {} 87 | 88 | export const GeneratedFieldSchema = z.object({ 89 | type: GeneratedFieldTypeSchema.nullish(), 90 | name: z.string().nullish(), 91 | isComputed: z.boolean().nullish(), 92 | isOptional: z.boolean().nullish(), 93 | hasDefault: z.boolean().nullish() 94 | }); 95 | 96 | /** 97 | * Specifies options for the generated field mapping 98 | * 99 | * The options here are ts-sql-query specific 100 | */ 101 | export interface GeneratedField extends z.TypeOf {} 102 | 103 | export const FieldMappingSchema = z.object({ 104 | /** Optional criteria (string or regex) to match column name */ 105 | columnName: StrOrRegExpSchema.nullish(), 106 | 107 | /** Optional criteria (string or regex) to match table name */ 108 | tableName: StrOrRegExpSchema.nullish(), 109 | 110 | /** 111 | * Optional criteria (string or regex) to match column type (in database) 112 | * 113 | * This will be used to match against the type name as 114 | * present in the tbls output schema yaml file. 115 | */ 116 | columnType: StrOrRegExpSchema.nullish(), 117 | 118 | /** 119 | * Can be used to customize the field name or type mapping 120 | * in the generated field. 121 | * 122 | * Set to false to omit mapping of this field 123 | */ 124 | generatedField: GeneratedFieldSchema.or(z.literal(false)), 125 | 126 | comment: z.string().nullish(), 127 | }); 128 | 129 | export interface FieldMapping extends z.TypeOf {} 130 | 131 | export const fieldMappings: FieldMapping[] = [ 132 | { 133 | columnType: /(char|text)/i, 134 | generatedField: { type: { dbType: { name: "string" } } }, 135 | }, 136 | { 137 | columnType: /bool/i, 138 | generatedField: { type: { dbType: { name: "boolean" } } }, 139 | }, 140 | { 141 | columnType: /bigint|bigserial/i, 142 | generatedField: { type: { dbType: { name: "bigint" } } }, 143 | }, 144 | { 145 | columnType: /int/i, 146 | generatedField: { type: { dbType: { name: "int" } } }, 147 | }, 148 | { 149 | columnType: /^uuid$/i, 150 | generatedField: { type: { dbType: { name: "uuid" } } }, 151 | }, 152 | { 153 | columnType: /(timestamp|datetime)/i, 154 | generatedField: { type: { dbType: { name: "localDateTime" } } }, 155 | }, 156 | { 157 | columnType: /date/i, 158 | generatedField: { type: { dbType: { name: "localDate" } } }, 159 | }, 160 | { 161 | columnType: /time/i, 162 | generatedField: { type: { dbType: { name: "localTime" } } }, 163 | }, 164 | { 165 | columnType: /(double|float)/i, 166 | generatedField: { type: { dbType: { name: "double" } } }, 167 | }, 168 | { 169 | columnType: /(decimal|numeric)/i, 170 | generatedField: { type: { dbType: { name: "stringDouble" } } }, 171 | } 172 | ]; 173 | -------------------------------------------------------------------------------- /src/file-remover.ts: -------------------------------------------------------------------------------- 1 | import fs from "node:fs/promises"; 2 | import * as z from "zod"; 3 | import { glob } from "glob"; 4 | import path from "path/posix"; 5 | import prompts from "prompts"; 6 | import { GeneratorOptsSchema } from "./generator-options"; 7 | import { Logger } from "./logger" 8 | 9 | export class FileRemover { 10 | constructor( 11 | private opts: z.output, 12 | private writtenFiles: Set, 13 | private logger: Logger 14 | ) { } 15 | public async removeExtraneousFiles() { 16 | if (!this.opts.removeExtraneous || this.opts.removeExtraneous === 'never') { 17 | return; 18 | } 19 | const paths = await glob('*', { 20 | cwd: path.resolve(this.opts.outputDirPath) 21 | }); 22 | const extraneousPaths = paths.filter(p => !this.writtenFiles.has(p)) 23 | if (!extraneousPaths.length) { 24 | return 25 | } 26 | let pathsToDelete: string[] = [] 27 | if (this.opts.removeExtraneous === 'all') { 28 | pathsToDelete = extraneousPaths 29 | } else if (this.opts.removeExtraneous === 'interactively') { 30 | const { selection } = await prompts({ 31 | type: 'select', 32 | name: 'selection', 33 | message: `${extraneousPaths.length} extraneous files found after code generation. Select ones to delete:`, 34 | choices: [ 35 | { title: 'All', value: '$all' }, 36 | { title: 'None', value: '$none' }, 37 | { title: 'Select Individually', value: '$pick' } 38 | ] 39 | }) 40 | switch (selection) { 41 | case '$all': { 42 | pathsToDelete = extraneousPaths 43 | break; 44 | } 45 | case '$pick': { 46 | const { candidates } = await prompts({ 47 | type: 'multiselect', 48 | name: 'candidates', 49 | message: `Select files to delete:`, 50 | choices: extraneousPaths.map(value => ({ title: value, value })), 51 | hint: '- Space to select. Return to submit' 52 | }) 53 | pathsToDelete.push(...candidates) 54 | break; 55 | } 56 | } 57 | } 58 | this.logger.info(`Deleting ${pathsToDelete.length} files: `, pathsToDelete.join(', ')) 59 | await Promise.all(pathsToDelete.map((p) => 60 | fs.rm(path.join(this.opts.outputDirPath, p), { 61 | recursive: true 62 | })) 63 | ) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/generator-options.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | import { FieldMappingSchema, ImportedItemSchema, StrOrRegExpSchema } from "./field-mappings"; 3 | 4 | export const TableInclusionSchema = z.object({ 5 | /** 6 | * Tables to be included - identified by qualified table name 7 | * or regular expression 8 | */ 9 | include: StrOrRegExpSchema.array().nullish(), 10 | /** 11 | * Tables to be excluded - identified by qualified table name 12 | * or regular expression 13 | */ 14 | exclude: StrOrRegExpSchema.array().nullish(), 15 | }); 16 | 17 | export interface TableInclusion extends z.input {} 18 | 19 | export const ExportTypesOptionsSchema = z.object({ 20 | /** 21 | * If enabled, instead of type alias we will generate interfaces 22 | * 23 | * This can make type errors more succinct. 24 | */ 25 | asInterface: z.boolean(), 26 | }); 27 | 28 | export interface ExportTypesOptions extends z.input {} 29 | 30 | export const ExportOptionsSchema = z.object({ 31 | /** 32 | * In addition to the table class, also expose instantiated instance of table class 33 | * 34 | * Example: 35 | * export class UserTable extends Table { ... } 36 | * 37 | * export const tUserTable = new UserTable() // <---- 38 | */ 39 | tableInstances: z.boolean().default(false), 40 | 41 | /** 42 | * If set to false, prevents the table class from getting exported 43 | * 44 | * This is useful in conjunction with tableInstances, if you only want to 45 | * export the table instance 46 | */ 47 | tableClasses: z.boolean().default(true), 48 | 49 | /** 50 | * Additionally export the row types associated with table 51 | * 52 | * Example: 53 | * import { InsertableRow, UpdatableRow, SelectedRow } from "ts-sql-query/extras/types" 54 | * 55 | * export class UserTable extends Table { ... } 56 | * 57 | * // Type of user row that can be used for insert 58 | * // Here computed columns will not be present and columns with defaults will be optional 59 | * export type UserIRow = InsertableRow 60 | * 61 | * // Type of user row that can be used for update 62 | * // Here computed columns will not be present and all fields will be optional 63 | * export type UserURow = UpdatableRow 64 | * 65 | * // Type of user row that is returned from select 66 | * // Here computed columns will be present, only nullable fields will be optional 67 | * export type UserSRow = SelectedRow 68 | * 69 | */ 70 | rowTypes: z.boolean().or(ExportTypesOptionsSchema).default(false), 71 | 72 | /** 73 | * Additionally export the value types associated with table 74 | * 75 | * Example: 76 | * import { InsertableValues, UpdatableValues, SelectedValues } from "ts-sql-query/extras/types" 77 | * 78 | * export class UserTable extends Table { ... } 79 | * 80 | * // Type of user values that can be used for insert 81 | * // Here computed columns will not be present and columns with defaults will be optional 82 | * export type InsertableUser = InsertableValues 83 | * 84 | * // Type of user values that can be used for update 85 | * // Here computed columns will not be present and all fields will be optional 86 | * export type UpdatableUser = UpdatableValues 87 | * 88 | * // Type of user values that is returned from select 89 | * // Here computed columns will be present, only nullable fields will be optional 90 | * export type User = SelectedValues 91 | * 92 | */ 93 | valuesTypes: z.boolean().or(ExportTypesOptionsSchema).default(false), 94 | 95 | /** 96 | * Additionally export the extracted columns (Useful for select * queries etc.) 97 | * 98 | * Example: 99 | * export const tUserCols = extractColumnsFrom(tUser) 100 | */ 101 | extractedColumns: z.boolean().default(false), 102 | 103 | /** 104 | * Additionally export a column types mapping useful for constructing filter type 105 | * for dynamic conditions. 106 | * 107 | * Example: 108 | * export type UserCols = { 109 | * id: 'int' 110 | * name: 'string' 111 | * } 112 | */ 113 | columnTypeMappingInterface: z.boolean().default(false), 114 | 115 | /** 116 | * Generate a repository class to simplify common single-table CRUD operations 117 | * 118 | * This is currently only supported for tables having an id column as primary key 119 | */ 120 | crudRepository: z.boolean().default(false), 121 | }); 122 | 123 | export interface ExportOptions extends z.input {} 124 | 125 | export const NamingOptionsSchema = z.object({ 126 | /** 127 | * Prefix to be used in the name of the class that reprecents a table 128 | */ 129 | tableClassNamePrefix: z.string().default(''), 130 | /** 131 | * Suffix to be used in the name of the class that reprecents a table 132 | */ 133 | tableClassNameSuffix: z.string().default('Table'), 134 | /** 135 | * Prefix to be used in the name of the class that reprecents a view 136 | */ 137 | viewClassNamePrefix: z.string().default(''), 138 | /** 139 | * Suffix to be used in the name of the class that reprecents a view 140 | */ 141 | viewClassNameSuffix: z.string().default('Table'), 142 | /** 143 | * Prefix to be used in the name of the instance of the class that reprecents a table 144 | */ 145 | tableInstanceNamePrefix: z.string().default('t'), 146 | /** 147 | * Suffix to be used in the name of the instance of the class that reprecents a table 148 | */ 149 | tableInstanceNameSuffix: z.string().default(''), 150 | /** 151 | * Prefix to be used in the name of the instance of the class that reprecents a view 152 | */ 153 | viewInstanceNamePrefix: z.string().default('t'), 154 | /** 155 | * Suffix to be used in the name of the the instance of class that reprecents a view 156 | */ 157 | viewInstanceNameSuffix: z.string().default(''), 158 | /** 159 | * Prefix to be used in the name of the InsertableRow type 160 | */ 161 | insertableRowTypeNamePrefix: z.string().default(''), 162 | /** 163 | * Suffix to be used in the name of the InsertableRow type 164 | */ 165 | insertableRowTypeNameSuffix: z.string().default('IRow'), 166 | /** 167 | * Prefix to be used in the name of the UpdatableRow type 168 | */ 169 | updatableRowTypeNamePrefix: z.string().default(''), 170 | /** 171 | * Suffix to be used in the name of the UpdatableRow type 172 | */ 173 | updatableRowTypeNameSuffix: z.string().default('URow'), 174 | /** 175 | * Prefix to be used in the name of the SelectedRow type 176 | */ 177 | selectedRowTypeNamePrefix: z.string().default(''), 178 | /** 179 | * Suffix to be used in the name of the SelectedRow type 180 | */ 181 | selectedRowTypeNameSuffix: z.string().default('SRow'), 182 | /** 183 | * Prefix to be used in the name of the InsertableValues type 184 | */ 185 | insertableValuesTypeNamePrefix: z.string().default('Insertable'), 186 | /** 187 | * Suffix to be used in the name of the InsertableValues type 188 | */ 189 | insertableValuesTypeNameSuffix: z.string().default(''), 190 | /** 191 | * Prefix to be used in the name of the UpdatableValues type 192 | */ 193 | updatableValuesTypeNamePrefix: z.string().default('Updatable'), 194 | /** 195 | * Suffix to be used in the name of the UpdatableValues type 196 | */ 197 | updatableValuesTypeNameSuffix: z.string().default(''), 198 | /** 199 | * Prefix to be used in the name of the SelectedValues type 200 | */ 201 | selectedValuesTypeNamePrefix: z.string().default(''), 202 | /** 203 | * Suffix to be used in the name of the SelectedValues type 204 | */ 205 | selectedValuesTypeNameSuffix: z.string().default(''), 206 | /** 207 | * Prefix to be used in the name of the const with the column list of a table 208 | */ 209 | tableColumnsNamePrefix: z.string().default('t'), 210 | /** 211 | * Suffix to be used in the name of the const with the column list of a table 212 | */ 213 | tableColumnsNameSuffix: z.string().default('Cols'), 214 | /** 215 | * Prefix to be used in the name of the const with the column list of a view 216 | */ 217 | viewColumnsNamePrefix: z.string().default('t'), 218 | /** 219 | * Suffix to be used in the name of the const with the column list of a view 220 | */ 221 | viewColumnsNameSuffix: z.string().default('Cols'), 222 | 223 | columnTypeMappingInterfaceNameSuffix: z.string().default('Cols'), 224 | 225 | crudRepositoryClassNamePrefix: z.string().default(''), 226 | 227 | crudRepositoryClassNameSuffix: z.string().default('CrudRepo'), 228 | }); 229 | 230 | export interface NamingOptions extends z.input {} 231 | 232 | export const CommonTypeAdapterOptionsSchema = z.object({ 233 | /** 234 | * Common import path to be used for type adapters 235 | * when no specific import path is specified at field level 236 | */ 237 | importPath: z.string(), 238 | }); 239 | 240 | export interface CommonTypeAdapterOptions 241 | extends z.input {} 242 | 243 | export const TableMappingSchema = z.object({ 244 | /** 245 | * Specify a prefix that will be prepended to the table name passed as generic parameter to Table type 246 | * This can be used for disambiguation when there can be multiple tables from different schema etc. 247 | */ 248 | idPrefix: z.string().nullish(), 249 | /** 250 | * Include the schema name in the table identifier passed to ts-sql-query 251 | */ 252 | useQualifiedTableName: z.boolean().nullish(), 253 | }); 254 | 255 | export interface TableMapping extends z.input {} 256 | 257 | export const CommonPrimaryKeyOptionsSchema = z.object({ 258 | /** 259 | * Name of primary key column 260 | */ 261 | name: z.string().nullish(), 262 | /** 263 | * If primary key column is auto-generated 264 | */ 265 | isAutoGenerated: z.boolean().nullish(), 266 | }); 267 | 268 | export interface CommonPrimaryKeyOptions 269 | extends z.input {} 270 | 271 | export const CommonCustomTypesOptionsSchema = z.object({ 272 | /** 273 | * Path from where custom types will be imported by default 274 | * 275 | * Relative to cwd 276 | */ 277 | importPath: z.string(), 278 | }); 279 | 280 | export interface CommonCustomTypesOptions 281 | extends z.input {} 282 | 283 | export const CommonOptionsSchema = z.object({ 284 | /** @see CommonCustomTypesOptions */ 285 | customTypes: CommonCustomTypesOptionsSchema.nullish(), 286 | 287 | /** @see CommonPrimaryKeyOptions */ 288 | typeAdapter: CommonTypeAdapterOptionsSchema.nullish(), 289 | 290 | /** @see CommonCustomTypesOptions */ 291 | primaryKey: CommonPrimaryKeyOptionsSchema.nullish(), 292 | }); 293 | 294 | export interface CommonOptions extends z.input {} 295 | 296 | export const RawContentSchema = z.object({ 297 | /** Raw content injected before generated code in each file */ 298 | before: z.string().nullish(), 299 | 300 | /** Raw content injected after generated code in each file */ 301 | after: z.string().nullish() 302 | }); 303 | 304 | export interface RawContent extends z.input {} 305 | 306 | export const TypeWrapperSchema = z.object({ 307 | typeName: StrOrRegExpSchema, 308 | wrapper: ImportedItemSchema 309 | }); 310 | 311 | export const OutputImportOptionsSchema = z.object({ 312 | extension: z.string().nullish(), 313 | }); 314 | 315 | export const OutputOptionsSchema = z.object({ 316 | import: OutputImportOptionsSchema.nullish(), 317 | }); 318 | 319 | export const ConnectionSourceOptionsSchema = z.object({ 320 | path: z.string().nullish(), 321 | resolveRelative: z.boolean().nullish(), 322 | }); 323 | 324 | export interface ConnectionSourceOptions extends z.input {} 325 | 326 | export const GeneratorOptsSchema = z.object({ 327 | /** Root path of module - used for resolving relative paths. If unspecified, assumed to be cwd */ 328 | moduleRoot: z.string().nullish(), 329 | 330 | /** Simulate the generation and print the outcome without actually modifying any files */ 331 | dryRun: z.boolean().nullish(), 332 | 333 | /** Path to yaml schema dumped by tbls */ 334 | schemaPath: z 335 | .string() 336 | .nullish() 337 | .transform((it) => it ?? "schema.yaml"), 338 | 339 | /** 340 | * Path to module that exports DBConnection object used in table mappers 341 | * @deprecated 342 | * @see connectionSource 343 | */ 344 | connectionSourcePath: z 345 | .string() 346 | .nullish() 347 | .transform((it) => it ?? "src/db/connection-source.ts"), 348 | 349 | /** 350 | * Connection source configuration 351 | * @see ConnectionSourceOptions 352 | */ 353 | connectionSource: ConnectionSourceOptionsSchema.nullish(), 354 | 355 | /** Path to output directory where a typescript class file will be generated for each table */ 356 | outputDirPath: z 357 | .string() 358 | .nullish() 359 | .transform((it) => it ?? "src/generated"), 360 | 361 | /** 362 | * Customize how table columns are mapped to typescript fields 363 | * 364 | * @see FieldMapping 365 | */ 366 | fieldMappings: FieldMappingSchema.array().nullish(), 367 | 368 | /** 369 | * Customize how tables are mapped 370 | * 371 | * @see TableMapping 372 | */ 373 | tableMapping: TableMappingSchema.nullish(), 374 | 375 | /** 376 | * Restrict the generator to process only a subset of tables 377 | * available 378 | * 379 | * @see TableInclusion 380 | */ 381 | tables: TableInclusionSchema.nullish(), 382 | 383 | /** 384 | * Shared options that affect all generated output 385 | */ 386 | output: OutputOptionsSchema.nullish(), 387 | 388 | /** 389 | * Customize what all entities are exported from generated file 390 | * 391 | * @see ExportOptions 392 | */ 393 | export: ExportOptionsSchema.partial().nullish(), 394 | 395 | /** 396 | * Convenience utility for common cases where all tables 397 | * follow same conventions 398 | * 399 | * See {@link CommonOptions} 400 | */ 401 | common: CommonOptionsSchema.nullish(), 402 | 403 | /** 404 | * Customize the naming rules of the generated items 405 | * 406 | * See NamingOptions 407 | */ 408 | naming: NamingOptionsSchema.partial().nullish(), 409 | 410 | /** 411 | * The fields marked as "custom", "customComparable" or "enum" receive a second generic 412 | * argument that need to be the same of the db type in the database or redefined for the field 413 | * If you set to true this property that second generic argument will be generated. 414 | */ 415 | includeDBTypeWhenIsOptional: z.boolean().nullish(), 416 | 417 | /** 418 | * Remove extraneous files after code generation completes - this prevents you from 419 | * having to manually clean up files after eg. any table has been deleted, but it is 420 | * your responsibility to ensure that the outputDir used solely for files generated through 421 | * this utility and all files are written as part of single run. 422 | * 423 | * Defauls to retaining all extraneous files. 424 | */ 425 | removeExtraneous: z.enum([ 426 | 'never', 427 | 'interactively', 428 | 'all' 429 | ]).nullish(), 430 | 431 | /** 432 | * Support injection of raw content in the generated files. 433 | * This is useful for adding things like eslint-disable, additional exports etc. 434 | * 435 | * @see RawContent 436 | */ 437 | rawContent: RawContentSchema.nullish(), 438 | 439 | /** 440 | * Wrap inferred types before exporting - this is useful to restrict 441 | * the types used for insert/update etc. beyond what the database permits. 442 | * 443 | * Eg. We can hint that updatedAt must be set whenever record is updated 444 | * 445 | * @see TypeWapper 446 | */ 447 | typeWrappers: TypeWrapperSchema.array().nullish(), 448 | }); 449 | 450 | /** 451 | * Generator options 452 | */ 453 | export interface GeneratorOpts extends z.input {} 454 | -------------------------------------------------------------------------------- /src/generator.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs/promises"; 2 | import * as z from "zod"; 3 | import Handlebars from "handlebars"; 4 | import { register } from "hbs-dedent-helper"; 5 | import yaml from "js-yaml"; 6 | import { isObject, camelCase, isEmpty, last, memoize, upperFirst } from "lodash"; 7 | import path from "path/posix"; 8 | import { match } from "ts-pattern"; 9 | import { 10 | fieldMappings, 11 | GeneratedField, 12 | GeneratedFieldType, 13 | ImportedItem 14 | } from "./field-mappings"; 15 | import { FileRemover } from "./file-remover"; 16 | import { GeneratorOpts, GeneratorOptsSchema, NamingOptions, NamingOptionsSchema } from "./generator-options"; 17 | import { doesMatchNameOrPattern, doesMatchNameOrPatternNamespaced } from "./matcher"; 18 | import { Logger } from "./logger" 19 | import { Column, Table, TblsSchema } from "./tbls-types"; 20 | 21 | register(); 22 | 23 | type ColumnMethod = 24 | | "column" 25 | | "optionalColumn" 26 | | "columnWithDefaultValue" 27 | | "optionalColumnWithDefaultValue" 28 | | "computedColumn" 29 | | "optionalComputedColumn" 30 | | "primaryKey" 31 | | "autogeneratedPrimaryKey"; 32 | 33 | interface FieldTmplInput { 34 | name: string; 35 | isPK: boolean; 36 | columnMethod: ColumnMethod; 37 | columnName: string; 38 | isOptional: boolean; 39 | hasDefault: boolean; 40 | fieldType: GeneratedFieldType; 41 | includeDBTypeWhenIsOptional: boolean; 42 | } 43 | 44 | interface ImportTmplInput { 45 | importPath: string; 46 | imported: string[]; 47 | isDefault: boolean; 48 | } 49 | 50 | interface RepoInput { 51 | className: string 52 | methods: Record 53 | } 54 | 55 | /** 56 | * Generator class for programmatic codegen. 57 | * 58 | * Most common usage involves creating an instance and calling generate function: 59 | * 60 | * ```ts 61 | * const options = { 62 | * schemaPath: './schema.yaml', 63 | * connectionSourcePath: './connection-source.ts' 64 | * } 65 | * const generator = new Generator(options); 66 | * await generator.generate(); 67 | * ``` 68 | * 69 | * See [GeneratorOpts](../interfaces/GeneratorOpts.md) for configuration options. 70 | * 71 | * For advanced use-cases, you can extend this class. 72 | * This enables you to use custom templates, pre/post processing of generated code 73 | * and custom logic for table/column/field mapping. 74 | */ 75 | export class Generator { 76 | protected opts: z.output; 77 | protected naming: NamingOptions; 78 | 79 | private writtenFiles = new Set() 80 | public logger: Logger = console; 81 | 82 | constructor(opts: GeneratorOpts) { 83 | this.opts = GeneratorOptsSchema.parse(opts); 84 | this.naming = NamingOptionsSchema.parse(this.opts.naming || {}); 85 | } 86 | 87 | protected getFieldMappings = memoize(() => { 88 | return (this.opts.fieldMappings ?? []).concat(fieldMappings); 89 | }); 90 | 91 | protected getTemplatePath = memoize(() => { 92 | return path.join(__dirname, "template.ts.hbs"); 93 | }); 94 | 95 | protected getCompiledTemplate = memoize(async () => { 96 | const rawTemplate = await fs.readFile(this.getTemplatePath(), "utf8"); 97 | return Handlebars.compile(rawTemplate, { 98 | noEscape: true 99 | }); 100 | }); 101 | 102 | private resolvePath(relPath: string) { 103 | if (this.opts.moduleRoot) { 104 | return path.resolve(this.opts.moduleRoot, relPath) 105 | } 106 | return path.resolve(relPath); 107 | } 108 | 109 | async generate() { 110 | const rawSchema = await fs.readFile( 111 | this.resolvePath(this.opts.schemaPath), 112 | "utf8" 113 | ); 114 | const schema = TblsSchema.parse(yaml.load(rawSchema)); 115 | await Promise.all( 116 | schema.tables.map(async (table) => { 117 | if (this.shouldProcess(table)) { 118 | await this.generateTableMapper(table); 119 | } 120 | }) 121 | ); 122 | await new FileRemover( 123 | this.opts, 124 | this.writtenFiles, 125 | this.logger 126 | ).removeExtraneousFiles() 127 | } 128 | 129 | protected shouldProcess(table: Table) { 130 | const filter = this.opts.tables; 131 | if ( 132 | filter?.include && 133 | filter.include.findIndex((it) => 134 | doesMatchNameOrPatternNamespaced(it, table.name) 135 | ) < 0 136 | ) { 137 | return false; 138 | } 139 | if ( 140 | filter?.exclude && 141 | filter.exclude.findIndex((it) => 142 | doesMatchNameOrPatternNamespaced(it, table.name) 143 | ) >= 0 144 | ) { 145 | return false; 146 | } 147 | return true; 148 | } 149 | 150 | protected getTableKind(table: Table): TableKind | null { 151 | return match(table.type.toLowerCase()) 152 | .with("base table", () => "Table" as const) 153 | .with("table", () => "Table" as const) 154 | .with("view", () => "View" as const) 155 | .with("materialized view", () => "View" as const) 156 | .otherwise(() => null); 157 | } 158 | 159 | protected async getTableTemplateInput(table: Table, tableKind: TableKind, filePath: string) { 160 | // Qualified table name with schema prefix 161 | const tableName = this.extractTableName(table.name); 162 | const pkCol = this.findPrimaryKey(table); 163 | const fields = this.getFieldsInput(table, pkCol); 164 | const dbConnectionSource = this.getConnectionSourceImportPath(filePath); 165 | const exportTableClass = this.opts.export?.tableClasses ?? true; 166 | const wrapperTypeImports: ImportTmplInput[] = []; 167 | const className = this.getTableMapperClassName(table.name, tableKind); 168 | const rowTypes = this.getRowTypeInputs(tableName, tableKind, className, wrapperTypeImports); 169 | const valuesTypes = this.getValuesTypeInputs(tableName, tableKind, className, wrapperTypeImports); 170 | const pkField = fields.find(it => it.isPK) 171 | const repo = this.getRepoInput(tableName, tableKind, pkField) 172 | const colMapping = this.getColMappingInput(tableName, !!repo) 173 | const colSetName = this.getColSetName(tableName, tableKind); 174 | const instName = this.getTableMapperInstName(tableName, tableKind) 175 | const idPrefix = this.getIdPrefix(table); 176 | const rowTypePrefix = this.getRowTypePrefix(tableName); 177 | 178 | const adapterImports = this.getAdapterImports(filePath, fields); 179 | const typeImports = this.getTypeImports(filePath, fields, !!repo); 180 | const utilImports = this.getUtilImports(colSetName, !!repo) 181 | 182 | return this.preProcessTemplateInput({ 183 | table: { 184 | name: this.opts.tableMapping?.useQualifiedTableName 185 | ? table.name 186 | : tableName, 187 | kind: tableKind, 188 | comment: this.formatComment([table.comment]), 189 | idPrefix, 190 | }, 191 | output: this.opts.output, 192 | imports: [ 193 | ...utilImports, 194 | ...adapterImports, 195 | ...typeImports, 196 | ...wrapperTypeImports, 197 | ], 198 | dbConnectionSource, 199 | className, 200 | instName, 201 | fields, 202 | adapterImports, 203 | exportTableClass, 204 | rowTypes, 205 | valuesTypes, 206 | importExtraTypes: rowTypes || valuesTypes, 207 | rowTypePrefix, 208 | colSetName, 209 | colMapping, 210 | pkField, 211 | repo 212 | }); 213 | } 214 | 215 | protected getFieldsInput(table: Table, pkCol: Column | null): FieldTmplInput[] { 216 | return table.columns 217 | .filter((col) => { 218 | return !this.isColumnOmitted(table.name, col); 219 | }) 220 | .map((col) => this.getFieldInput(col, table, pkCol)); 221 | } 222 | 223 | protected getFieldInput(col: Column, table: Table, pkCol: Column | null) { 224 | const isOptional = this.isColumnOptional(table.name, col); 225 | const hasDefault = this.doesColumnHaveDefault(table.name, col); 226 | const isComputed = this.isColumnComputed(table.name, col); 227 | const comment = this.getColComment(table.name, col); 228 | let isPK = false; 229 | let columnMethod!: ColumnMethod; 230 | if (col === pkCol) { 231 | const isAutoGenerated = 232 | col.default ?? 233 | this.opts.common?.primaryKey?.isAutoGenerated ?? 234 | false; 235 | columnMethod = isAutoGenerated 236 | ? "autogeneratedPrimaryKey" 237 | : "primaryKey"; 238 | isPK = true; 239 | } else if (isComputed) { 240 | if (isOptional) { 241 | columnMethod = "optionalComputedColumn"; 242 | } else { 243 | columnMethod = "computedColumn"; 244 | } 245 | } else if (!isOptional && !hasDefault) { 246 | columnMethod = "column"; 247 | } else if (isOptional && !hasDefault) { 248 | columnMethod = "optionalColumn"; 249 | } else if (isOptional && hasDefault) { 250 | columnMethod = "optionalColumnWithDefaultValue"; 251 | } else if (!isOptional && hasDefault) { 252 | columnMethod = "columnWithDefaultValue"; 253 | } 254 | return { 255 | name: this.getFieldNameForColumn(table.name, col), 256 | columnName: col.name, 257 | comment: this.formatComment([col.comment, comment]), 258 | isOptional, 259 | hasDefault, 260 | columnMethod, 261 | fieldType: this.getFieldType(table.name, col), 262 | includeDBTypeWhenIsOptional: this.opts.includeDBTypeWhenIsOptional || false, 263 | isPK, 264 | }; 265 | } 266 | 267 | protected getRepoInput(tableName: string, tableKind: TableKind, pkField?: FieldTmplInput): RepoInput | null { 268 | if (!pkField) return null; 269 | if (!this.opts.export?.crudRepository) return null; 270 | const pkFSuffix = upperFirst(pkField.name); 271 | 272 | const methods: Record = {}; 273 | 274 | methods.select = `select`; 275 | methods.selectWhere = `selectWhere`; 276 | 277 | methods.findAll = `findAll`; 278 | methods.findOne = `findOneBy${pkFSuffix}`; 279 | methods.findMany = `findManyBy${pkFSuffix}`; 280 | 281 | if (tableKind === 'Table') { 282 | methods.insert = `select`; 283 | methods.insertOne = `insertOne`; 284 | methods.insertMany = `insertMany`; 285 | 286 | methods.update = `update`; 287 | methods.updateOne = `updateOneBy${pkFSuffix}`; 288 | methods.updateMany = `updateManyBy${pkFSuffix}`; 289 | 290 | methods.delete = 'delete'; 291 | methods.deleteOne = `deleteOneBy${pkFSuffix}`; 292 | methods.deleteMany = `deleteManyBy${pkFSuffix}`; 293 | } 294 | return { 295 | className: this.getCrudRepoName(tableName), 296 | methods, 297 | } 298 | } 299 | 300 | protected async generateTableMapper(table: Table) { 301 | const tableKind = this.getTableKind(table); 302 | if (!tableKind) { 303 | this.logger.warn( 304 | `Unknown table type ${table.type} for table ${table.name}: SKIPPING` 305 | ); 306 | return; 307 | } 308 | const filePath = this.getOutputFilePath(table, tableKind); 309 | const templateInput = await this.getTableTemplateInput(table, tableKind, filePath) 310 | const template = await this.getCompiledTemplate(); 311 | const output = await this.postProcessOutput(template(templateInput), table); 312 | await fs.mkdir(path.dirname(filePath), { 313 | recursive: true 314 | }); 315 | if (this.opts.dryRun) { 316 | this.logger.info(`Will populate ${filePath} with:`); 317 | this.logger.info(output); 318 | this.logger.info("---"); 319 | } else { 320 | this.logger.info(`Writing ${filePath}`); 321 | this.writtenFiles.add(path.relative(this.opts.outputDirPath, filePath)); 322 | await fs.writeFile(filePath, output); 323 | } 324 | } 325 | 326 | protected getIdPrefix(table: Table) { 327 | let idPrefix = this.opts.tableMapping?.idPrefix; 328 | if (!idPrefix && this.opts.tableMapping?.useQualifiedTableName) { 329 | idPrefix = table.name 330 | .split(".") 331 | .slice(0, -1) 332 | .map((it) => upperFirst(camelCase(it))) 333 | .join(""); 334 | } 335 | return idPrefix; 336 | } 337 | 338 | protected formatComment(comments: (string | null | undefined)[]) { 339 | const neComments = comments.filter(Boolean) as string[] 340 | if (isEmpty(neComments)) return null; 341 | const commentLines = neComments 342 | .flatMap(c => c.split("\n")) 343 | .map(c => ` * ${c}`) 344 | commentLines.unshift('/**') 345 | commentLines.push(' */') 346 | return commentLines; 347 | } 348 | 349 | protected getConnectionSourceImportPath(outputFilePath: string) { 350 | const csPath = this.opts.connectionSource?.path ?? this.opts.connectionSourcePath; 351 | if (this.opts.connectionSource?.resolveRelative === false) { 352 | return csPath; 353 | } 354 | const relPath = path.relative( 355 | path.dirname(outputFilePath), 356 | this.resolvePath(csPath) 357 | ); 358 | return path.join( 359 | path.dirname(relPath), 360 | path.basename(relPath) 361 | ); 362 | } 363 | 364 | protected getAdapterImports( 365 | outputFilePath: string, 366 | fields: FieldTmplInput[] 367 | ): ImportTmplInput[] { 368 | const imports = new Map>(); 369 | const defaultImports = new Map>(); 370 | for (const field of fields) { 371 | const adapter = field.fieldType?.adapter; 372 | if (!adapter) continue; 373 | const importPath = this.getAdapterImportPath(adapter, outputFilePath); 374 | let adapterImports; 375 | const map = adapter.isDefault ? defaultImports : imports; 376 | adapterImports = map.get(importPath) ?? new Set(); 377 | map.set(importPath, adapterImports); 378 | adapterImports.add(adapter.name); 379 | } 380 | return this.accumulateImports(imports, defaultImports); 381 | } 382 | 383 | private accumulateImports( 384 | imports: Map>, 385 | defaultImports: Map> 386 | ) { 387 | const inputs: ImportTmplInput[] = []; 388 | for (const [entries, isDefault] of [ 389 | [imports.entries(), false], 390 | [defaultImports.entries(), true], 391 | ] as const) { 392 | for (const [importPath, importedSet] of entries) { 393 | inputs.push({ 394 | importPath, 395 | imported: [...importedSet], 396 | isDefault, 397 | }); 398 | } 399 | } 400 | return inputs; 401 | } 402 | 403 | protected getTypeImports( 404 | outputFilePath: string, 405 | fields: FieldTmplInput[], 406 | generateRepo: boolean 407 | ): ImportTmplInput[] { 408 | const imports = new Map>(); 409 | const defaultImports = new Map>(); 410 | for (const field of fields) { 411 | const tsType = field.fieldType.tsType; 412 | if (!tsType) continue; 413 | const importPath = tsType.importPath; 414 | const name = tsType.name; 415 | if (!importPath || !name) { 416 | continue; 417 | } 418 | const nImportPath = this.getImportPathForOutputPath( 419 | outputFilePath, 420 | importPath, 421 | tsType 422 | ); 423 | const map = tsType.isDefault ? defaultImports : imports; 424 | const typeImports = map.get(nImportPath) ?? new Set(); 425 | map.set(nImportPath, typeImports); 426 | typeImports.add(name); 427 | } 428 | const importList = this.accumulateImports(imports, defaultImports); 429 | if (generateRepo) { 430 | importList.push({ 431 | importPath: "ts-sql-query/expressions/dynamicConditionUsingFilters", 432 | imported: ["DynamicCondition"], 433 | isDefault: false, 434 | }) 435 | } 436 | return importList; 437 | } 438 | 439 | protected getUtilImports(colSetName: string | null, generateRepo: boolean) { 440 | const imports: ImportTmplInput[] = [] 441 | if (colSetName || generateRepo) { 442 | imports.push({ 443 | importPath: "ts-sql-query/extras/utils", 444 | imported: ["extractColumnsFrom"], 445 | isDefault: false 446 | }) 447 | } 448 | return imports; 449 | } 450 | 451 | protected getImportPathForOutputPath( 452 | filePath: string, 453 | importPath: string, 454 | importedItem: ImportedItem 455 | ) { 456 | if (importedItem.isRelative === false) return importPath; 457 | const result: string = path.relative( 458 | path.dirname(filePath), 459 | this.resolvePath(importPath) 460 | ); 461 | if (result.startsWith(".")) { 462 | return result; 463 | } else { 464 | return "./" + result; 465 | } 466 | } 467 | 468 | protected getAdapterImportPath( 469 | adapter: ImportedItem, 470 | outputFilePath: string 471 | ) { 472 | const relImportPath = 473 | adapter.importPath ?? this.opts.common?.typeAdapter?.importPath; 474 | if (!relImportPath) { 475 | throw new Error( 476 | `Unable to resolve import path for type adapter: ${JSON.stringify( 477 | adapter 478 | )}` 479 | ); 480 | } 481 | return this.getImportPathForOutputPath( 482 | outputFilePath, 483 | relImportPath, 484 | adapter 485 | ); 486 | } 487 | 488 | protected async preProcessTemplateInput(input: any) { 489 | return input; 490 | } 491 | 492 | protected async postProcessOutput(output: string, _table: Table) { 493 | const sections = [output]; 494 | if (this.opts.rawContent?.before) { 495 | sections.unshift(this.opts.rawContent.before) 496 | } 497 | if (this.opts.rawContent?.after) { 498 | sections.push(this.opts.rawContent.after) 499 | } 500 | return sections.join('\n'); 501 | } 502 | 503 | protected getCrudRepoName(tableName: string) { 504 | return this.naming.crudRepositoryClassNamePrefix + 505 | this.getPascalCasedTableName(tableName) + 506 | this.naming.crudRepositoryClassNameSuffix; 507 | } 508 | 509 | protected getTableMapperClassName(tableName: string, tableKind: TableKind) { 510 | if (tableKind === 'Table') { 511 | return this.naming.tableClassNamePrefix + 512 | this.getPascalCasedTableName(tableName) + 513 | this.naming.tableClassNameSuffix; 514 | } else { 515 | return this.naming.viewClassNamePrefix + 516 | this.getPascalCasedTableName(tableName) + 517 | this.naming.viewClassNameSuffix; 518 | } 519 | } 520 | 521 | protected getRowTypePrefix(tableName: string) { 522 | return this.getPascalCasedTableName(tableName); 523 | } 524 | 525 | protected getTableMapperInstanceName(tableName: string, tableKind: TableKind) { 526 | if (tableKind === 'Table') { 527 | return this.naming.tableInstanceNamePrefix + 528 | this.getPascalCasedTableName(tableName) + 529 | this.naming.tableInstanceNameSuffix; 530 | } else { 531 | return this.naming.viewInstanceNamePrefix + 532 | this.getPascalCasedTableName(tableName) + 533 | this.naming.viewInstanceNameSuffix; 534 | } 535 | } 536 | 537 | protected getColumnsObjectName(tableName: string, tableKind: TableKind) { 538 | if (tableKind === 'Table') { 539 | if (this.naming.tableColumnsNamePrefix) { 540 | return this.naming.tableColumnsNamePrefix + 541 | this.getPascalCasedTableName(tableName) + 542 | this.naming.tableColumnsNameSuffix; 543 | } else { 544 | return this.getCamelCasedTableName(tableName) + 545 | this.naming.tableColumnsNameSuffix; 546 | } 547 | } else { 548 | if (this.naming.viewColumnsNamePrefix) { 549 | return this.naming.viewColumnsNamePrefix + 550 | this.getPascalCasedTableName(tableName) + 551 | this.naming.viewColumnsNameSuffix; 552 | } else { 553 | return this.getCamelCasedTableName(tableName) + 554 | this.naming.viewColumnsNameSuffix; 555 | } 556 | } 557 | } 558 | 559 | private getPascalCasedTableName(tableName: string) { 560 | return upperFirst(camelCase(last(tableName.split(".")))); 561 | } 562 | 563 | private getCamelCasedTableName(tableName: string) { 564 | return camelCase(last(tableName.split("."))); 565 | } 566 | 567 | protected isColumnOmitted(tableName: string, col: Column) { 568 | const mapping = this.getFieldMappings().find( 569 | (it) => 570 | it.generatedField === false && 571 | doesMatchNameOrPatternNamespaced(it.columnName, col.name) && 572 | doesMatchNameOrPatternNamespaced(it.tableName, tableName) && 573 | doesMatchNameOrPatternNamespaced(it.columnType, col.type) 574 | ); 575 | return !!mapping; 576 | } 577 | 578 | protected isColumnOptional(tableName: string, col: Column): boolean { 579 | const mapping = this.getFieldMappings().find( 580 | (it) => 581 | it.generatedField && 582 | it.generatedField.isOptional != null && 583 | doesMatchNameOrPatternNamespaced(it.columnName, col.name) && 584 | doesMatchNameOrPatternNamespaced(it.tableName, tableName) && 585 | doesMatchNameOrPatternNamespaced(it.columnType, col.type) 586 | ); 587 | if (mapping?.generatedField) { 588 | return mapping.generatedField.isOptional === true; 589 | } else { 590 | return col.nullable === true; 591 | } 592 | } 593 | 594 | protected doesColumnHaveDefault(tableName: string, col: Column): boolean { 595 | const mapping = this.getFieldMappings().find( 596 | (it) => 597 | it.generatedField && 598 | it.generatedField.hasDefault != null && 599 | doesMatchNameOrPatternNamespaced(it.columnName, col.name) && 600 | doesMatchNameOrPatternNamespaced(it.tableName, tableName) && 601 | doesMatchNameOrPatternNamespaced(it.columnType, col.type) 602 | ); 603 | if (mapping?.generatedField) { 604 | return mapping.generatedField.hasDefault === true; 605 | } else { 606 | return col.default != null; 607 | } 608 | } 609 | 610 | protected isColumnComputed(tableName: string, col: Column): boolean { 611 | const mapping = this.getFieldMappings().find( 612 | (it) => 613 | it.generatedField && 614 | it.generatedField.isComputed != null && 615 | doesMatchNameOrPatternNamespaced(it.columnName, col.name) && 616 | doesMatchNameOrPatternNamespaced(it.tableName, tableName) && 617 | doesMatchNameOrPatternNamespaced(it.columnType, col.type) 618 | ); 619 | if (mapping?.generatedField) { 620 | return mapping.generatedField.isComputed === true; 621 | } 622 | return false; 623 | } 624 | 625 | protected getColComment(tableName: string, col: Column): string | undefined { 626 | const mapping = this.getFieldMappings().find( 627 | (it) => 628 | it.comment && 629 | doesMatchNameOrPatternNamespaced(it.columnName, col.name) && 630 | doesMatchNameOrPatternNamespaced(it.tableName, tableName) && 631 | doesMatchNameOrPatternNamespaced(it.columnType, col.type) 632 | ); 633 | return mapping?.comment ?? undefined; 634 | } 635 | 636 | protected getFieldNameForColumn(tableName: string, col: Column) { 637 | const mapping = this.getFieldMappings().find( 638 | (it) => 639 | it.generatedField && 640 | it.generatedField?.name && 641 | doesMatchNameOrPatternNamespaced(it.columnName, col.name) && 642 | doesMatchNameOrPatternNamespaced(it.tableName, tableName) && 643 | doesMatchNameOrPatternNamespaced(it.columnType, col.type) 644 | ); 645 | return ( 646 | (mapping?.generatedField as GeneratedField)?.name ?? camelCase(col.name) 647 | ); 648 | } 649 | 650 | protected getFieldType(tableName: string, col: Column): GeneratedFieldType { 651 | const mapping = this.getFieldMappings().find( 652 | (it) => 653 | it.generatedField && 654 | it.generatedField?.type && 655 | doesMatchNameOrPatternNamespaced(it.columnName, col.name) && 656 | doesMatchNameOrPatternNamespaced(it.tableName, tableName) && 657 | doesMatchNameOrPatternNamespaced(it.columnType, col.type) 658 | ); 659 | if (!mapping) { 660 | throw new Error( 661 | `Failed to infer field type for ${tableName}.${col.name}` 662 | ); 663 | } 664 | const generatedField = mapping.generatedField as GeneratedField; 665 | const dbTypeName = generatedField.type?.dbType?.name ?? col.type; 666 | let tsTypeName = generatedField.type?.tsType?.name; 667 | if (generatedField?.type?.adapter && !tsTypeName) { 668 | tsTypeName = upperFirst(camelCase(dbTypeName)); 669 | } 670 | return { 671 | ...generatedField.type, 672 | dbType: { 673 | ...generatedField.type?.dbType, 674 | name: dbTypeName, 675 | }, 676 | tsType: { 677 | ...generatedField.type?.tsType, 678 | name: tsTypeName ?? "unknown", 679 | }, 680 | }; 681 | } 682 | 683 | protected getOutputFilePath(table: Table, tableKind: TableKind) { 684 | const fileName = this.getOutputFileName(table, tableKind); 685 | return path.join(this.opts.outputDirPath, fileName); 686 | } 687 | 688 | protected getOutputFileName(table: Table, tableKind: TableKind) { 689 | return this.getTableMapperClassName(table.name, tableKind) + ".ts"; 690 | } 691 | 692 | protected findPrimaryKey(table: Table) { 693 | let col: Column | null = null; 694 | const commonPKColName = this.opts.common?.primaryKey?.name; 695 | if (commonPKColName) { 696 | col = table.columns.find((it) => it.name === commonPKColName) ?? null; 697 | } 698 | if (!col) { 699 | const pkConstraint = table.constraints?.find( 700 | (it) => it.type === "PRIMARY KEY" 701 | ); 702 | if (pkConstraint && pkConstraint.columns.length === 1) { 703 | return table.columns.find((it) => it.name === pkConstraint.columns[0]) ?? null; 704 | } 705 | } 706 | return null; 707 | } 708 | 709 | protected wrapType(typeExpr: string, wrapper?: string | null) { 710 | if (!wrapper) return typeExpr; 711 | return `${wrapper}<${typeExpr}>`; 712 | } 713 | 714 | protected getTypeWrapper(typeName: string) { 715 | return this.opts.typeWrappers 716 | ?.find(it => { 717 | return doesMatchNameOrPattern(it.typeName, typeName) 718 | }) 719 | ?.wrapper 720 | } 721 | 722 | protected extractTableName(configTableName: string) { 723 | return last(configTableName.split(".")) as string; 724 | } 725 | 726 | protected getSelectedRowTypeName(tableName: string) { 727 | return this.naming.selectedRowTypeNamePrefix + 728 | this.getPascalCasedTableName(tableName) + 729 | this.naming.selectedRowTypeNameSuffix; 730 | } 731 | 732 | protected getInsertableRowTypeName(tableName: string) { 733 | return this.naming.insertableRowTypeNamePrefix + 734 | this.getPascalCasedTableName(tableName) + 735 | this.naming.insertableRowTypeNameSuffix; 736 | } 737 | 738 | protected getUpdatableRowTypeName(tableName: string) { 739 | return this.naming.updatableRowTypeNamePrefix + 740 | this.getPascalCasedTableName(tableName) + 741 | this.naming.updatableRowTypeNameSuffix; 742 | } 743 | 744 | protected getSelectedValuesTypeName(tableName: string) { 745 | return this.naming.selectedValuesTypeNamePrefix + 746 | this.getPascalCasedTableName(tableName) + 747 | this.naming.selectedValuesTypeNameSuffix; 748 | } 749 | 750 | protected getInsertableValuesTypeName(tableName: string) { 751 | return this.naming.insertableValuesTypeNamePrefix + 752 | this.getPascalCasedTableName(tableName) + 753 | this.naming.insertableValuesTypeNameSuffix; 754 | } 755 | 756 | protected getUpdatableValuesTypeName(tableName: string) { 757 | return this.naming.updatableValuesTypeNamePrefix + 758 | this.getPascalCasedTableName(tableName) + 759 | this.naming.updatableValuesTypeNameSuffix; 760 | } 761 | 762 | protected getColMappingObjName(tableName: string) { 763 | return this.getPascalCasedTableName(tableName) + this.naming.columnTypeMappingInterfaceNameSuffix; 764 | } 765 | 766 | protected getWrappedTypeInput(name: string, baseExpr: string, imports: ImportTmplInput[], isInterface = false) { 767 | const selectedWrapper = this.getTypeWrapper(name) 768 | if (selectedWrapper?.importPath) 769 | imports.push({ 770 | importPath: selectedWrapper.importPath, 771 | imported: [selectedWrapper.name], 772 | isDefault: !!selectedWrapper.isDefault 773 | }); 774 | return { 775 | name, 776 | isInterface, 777 | expr: this.wrapType( 778 | baseExpr, 779 | selectedWrapper?.name 780 | ) 781 | }; 782 | } 783 | 784 | protected getRowTypeInputs( 785 | tableName: string, 786 | tableKind: TableKind, 787 | mapperClassName: string, 788 | imports: ImportTmplInput[], 789 | ) { 790 | const isExported = this.opts.export?.rowTypes; 791 | const hasRepo = this.opts.export?.crudRepository; 792 | const isInterface = isObject(isExported) && isExported.asInterface; 793 | const rowTypes = (isExported || hasRepo) 794 | ? ({} as any) 795 | : false; 796 | if (rowTypes !== false) { 797 | rowTypes.selected = this.getWrappedTypeInput( 798 | this.getSelectedRowTypeName(tableName), 799 | `SelectedRow<${mapperClassName}>`, 800 | imports, 801 | isInterface 802 | ); 803 | rowTypes.selected.isExported = isExported; 804 | if (tableKind !== "View") { 805 | rowTypes.insertable = this.getWrappedTypeInput( 806 | this.getInsertableRowTypeName(tableName), 807 | `InsertableRow<${mapperClassName}>`, 808 | imports, 809 | isInterface 810 | ); 811 | rowTypes.insertable.isExported = isExported; 812 | rowTypes.updatable = this.getWrappedTypeInput( 813 | this.getUpdatableRowTypeName(tableName), 814 | `UpdatableRow<${mapperClassName}>`, 815 | imports, 816 | isInterface 817 | ); 818 | rowTypes.updatable.isExported = isExported; 819 | } 820 | } 821 | return rowTypes 822 | } 823 | 824 | protected getValuesTypeInputs( 825 | tableName: string, 826 | tableKind: TableKind, 827 | mapperClassName: string, 828 | imports: ImportTmplInput[] 829 | ) { 830 | const isExported = this.opts.export?.valuesTypes; 831 | const isInterface = isObject(isExported) && isExported.asInterface; 832 | const valuesTypes = isExported ? ({} as any) : false; 833 | if (valuesTypes !== false) { 834 | valuesTypes.selected = this.getWrappedTypeInput( 835 | this.getSelectedValuesTypeName(tableName), 836 | `SelectedValues<${mapperClassName}>`, 837 | imports, 838 | isInterface 839 | ); 840 | if (tableKind !== "View") { 841 | valuesTypes.insertable = this.getWrappedTypeInput( 842 | this.getInsertableValuesTypeName(tableName), 843 | `InsertableValues<${mapperClassName}>`, 844 | imports, 845 | isInterface 846 | ) 847 | valuesTypes.updatable = this.getWrappedTypeInput( 848 | this.getUpdatableValuesTypeName(tableName), 849 | `UpdatableValues<${mapperClassName}>`, 850 | imports, 851 | isInterface 852 | ); 853 | } 854 | } 855 | return valuesTypes; 856 | } 857 | 858 | protected getColSetName(tableName: string, tableKind: TableKind) { 859 | return this.opts.export?.extractedColumns 860 | ? this.getColumnsObjectName(tableName, tableKind) 861 | : null; 862 | } 863 | 864 | protected getTableMapperInstName(tableName: string, tableKind: TableKind) { 865 | return this.opts.export?.tableInstances || this.opts.export?.extractedColumns 866 | ? this.getTableMapperInstanceName(tableName, tableKind) 867 | : null; 868 | } 869 | 870 | protected getColMappingInput(tableName: string, didGenerateRepo: boolean) { 871 | const exportColMapping = (this.opts.export?.columnTypeMappingInterface || didGenerateRepo) 872 | ? ({} as any) 873 | : false; 874 | if (exportColMapping !== false) { 875 | exportColMapping.name = this.getColMappingObjName(tableName) 876 | } 877 | return exportColMapping 878 | } 879 | } 880 | 881 | 882 | type TableKind = "Table" | "View" 883 | -------------------------------------------------------------------------------- /src/help.hbs: -------------------------------------------------------------------------------- 1 | ts-sql-codegen is a simple utility for generating table mappers for relational databases 2 | 3 | Installation: 4 | 5 | Step 1: Install tbls and ensure it is available in path 6 | Refer: https://github.com/k1LoW/tbls#install 7 | Step 2: Install ts-sql-codegen' 8 | npm i --dev ts-sql-codegen 9 | 10 | Note: 11 | - Global installation (npm i -g ts-sql-codegen) can be convenient, but is preferrable 12 | to have ts-sql-codegen as a project dependency to avoid versioning issues. 13 | 14 | Usage: 15 | 16 | After every database schema change: 17 | 18 | Step 1: Generate yaml schema file from database using tbls 19 | Example: tbls out postgres://postgres:password@localhost:5432/testdb -t yaml -o schema.yaml 20 | Step 2: Pass this schema file to ts-sql-codegen 21 | Example: ts-sql-codegen --schema ./schema.yaml --output-dir ./src/generated --connection-source ./src/db/connection-source 22 | Above options are default, so you can also just run ts-sql-codegen 23 | 24 | Note: 25 | - All paths are relative to cwd 26 | 27 | Additional CLI Options: 28 | --remove-extraneous Remove extraneous files from output directory which were not generated in last run 29 | --export-table-instance Export singleton instance of generated table mapper class 30 | --export-row-types Export row types for mapped tables (See https://ts-sql-query.readthedocs.io/en/stable/advanced-usage/#utility-types) 31 | --export-values-types Export value types for mapped tables (See https://ts-sql-query.readthedocs.io/en/stable/advanced-usage/#utility-types) 32 | --export-extracted-columns Set to true to export result of extractColumnsFrom function from ts-sql-query. 33 | --export-table-classes Set to false to prevent mapper class from being exported 34 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | GeneratorOpts, 3 | ExportOptions, 4 | TableInclusion, 5 | CommonOptions, 6 | CommonPrimaryKeyOptions, 7 | CommonCustomTypesOptions, 8 | CommonTypeAdapterOptions, 9 | } from "./generator-options"; 10 | export { 11 | FieldMapping, 12 | StrOrRegExp, 13 | ImportedItem, 14 | GeneratedFieldType, 15 | GeneratedField, 16 | fieldMappings 17 | } from "./field-mappings"; 18 | export { Generator } from "./generator"; 19 | -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | export type Logger = Record< 2 | "debug" | "info" | "warn" | "error", 3 | (...args: any[]) => void 4 | >; 5 | 6 | -------------------------------------------------------------------------------- /src/matcher.ts: -------------------------------------------------------------------------------- 1 | export const doesMatchNameOrPattern = ( 2 | matcher: undefined | null | string | RegExp, 3 | target: string 4 | ) => { 5 | if (matcher == null) return true; 6 | if (typeof matcher === "string") { 7 | return matcher === target; 8 | } 9 | return target.match(matcher); 10 | }; 11 | 12 | export const doesMatchNameOrPatternNamespaced = ( 13 | matcher: undefined | null | string | RegExp, 14 | target: string 15 | ) => { 16 | if (matcher == null) return true; 17 | if (typeof matcher === "string") { 18 | const matcherParts = matcher.split("."); 19 | const targetParts = target.split("."); 20 | for (let i = 0; i < matcherParts.length; i++) { 21 | if ( 22 | targetParts[targetParts.length - 1 - i] !== 23 | matcherParts[matcherParts.length - 1 - i] 24 | ) { 25 | return false; 26 | } 27 | } 28 | return true; 29 | } 30 | return target.match(matcher); 31 | }; 32 | -------------------------------------------------------------------------------- /src/tbls-types.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | 3 | export const ColumnSchema = z.object({ 4 | name: z.string(), 5 | type: z.string(), 6 | nullable: z.boolean().optional(), 7 | default: z.any().nullish(), 8 | comment: z.string().nullish(), 9 | }); 10 | 11 | export type Column = z.TypeOf; 12 | 13 | export const ConstraintSchema = z.object({ 14 | name: z.string(), 15 | type: z.string(), 16 | table: z.string(), 17 | columns: z.string().array(), 18 | referencedTable: z.string().nullish(), 19 | referencedColumns: z.string().array().nullish(), 20 | comment: z.string().nullish(), 21 | }); 22 | 23 | export const TableSchema = z.object({ 24 | name: z.string(), 25 | type: z.string(), 26 | columns: ColumnSchema.array(), 27 | constraints: ConstraintSchema.array().nullish(), 28 | comment: z.string().nullish() 29 | }); 30 | 31 | export type Table = z.TypeOf; 32 | 33 | export const TblsSchema = z.object({ 34 | name: z.string(), 35 | tables: TableSchema.array(), 36 | }); 37 | 38 | -------------------------------------------------------------------------------- /src/template.ts.hbs: -------------------------------------------------------------------------------- 1 | {{#trim-trailing-whitespace}} 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { {{table.kind}} } from "ts-sql-query/{{table.kind}}{{output.import.extension}}"; 9 | import type { DBConnection } from "{{dbConnectionSource}}{{output.import.extension}}"; 10 | {{#if importExtraTypes}} 11 | import { 12 | {{#if rowTypes.insertable}} 13 | InsertableRow, 14 | {{/if}} 15 | {{#if rowTypes.updatable}} 16 | UpdatableRow, 17 | {{/if}} 18 | {{#if rowTypes.selected}} 19 | SelectedRow, 20 | {{else if repo}} 21 | SelectedRow, 22 | {{/if}} 23 | {{#if valuesTypes.insertable}} 24 | InsertableValues, 25 | {{/if}} 26 | {{#if valuesTypes.updatable}} 27 | UpdatableValues, 28 | {{/if}} 29 | {{#if valuesTypes.selected}} 30 | SelectedValues, 31 | {{/if}} 32 | } from "ts-sql-query/extras/types{{output.import.extension}}"; 33 | {{/if}} 34 | {{#each imports}} 35 | {{#if isDefault}} 36 | {{#each imported}} 37 | {{#dedent-by 4 "level"}} 38 | import {{.}} from "{{../importPath}}{{../../output.import.extension}}"; 39 | {{/dedent-by}} 40 | {{/each}} 41 | {{else}} 42 | {{#dedent-by 3 "level"}} 43 | import { 44 | {{#dedent-by 2 "level"}} 45 | {{#each imported}} 46 | {{.}}, 47 | {{/each}} 48 | {{/dedent-by}} 49 | } from "{{importPath}}{{../output.import.extension}}"; 50 | {{/dedent-by}} 51 | {{/if}} 52 | {{/each}} 53 | 54 | {{#each table.comment}} 55 | {{this}} 56 | {{/each}} 57 | {{#if exportTableClass}}export {{/if}}class {{className}} extends {{table.kind}} { 58 | {{#dedent-by 3 "level"}} 59 | {{#each fields}} 60 | {{#each this.comment}} 61 | {{this}} 62 | {{/each}} 63 | {{#if fieldType.kind}} 64 | {{#if includeDBTypeWhenIsOptional}} 65 | {{name}} = this.{{columnMethod}}<{{fieldType.tsType.name}}, '{{fieldType.dbType.name}}'>('{{columnName}}', '{{fieldType.kind}}', '{{fieldType.dbType.name}}'{{#if fieldType.adapter}}, {{fieldType.adapter.name}}{{/if}}); 66 | {{else}} 67 | {{name}} = this.{{columnMethod}}<{{fieldType.tsType.name}}>('{{columnName}}', '{{fieldType.kind}}', '{{fieldType.dbType.name}}'{{#if fieldType.adapter}}, {{fieldType.adapter.name}}{{/if}}); 68 | {{/if}} 69 | {{else}} 70 | {{name}} = this.{{columnMethod}}('{{columnName}}', '{{fieldType.dbType.name}}'{{#if fieldType.adapter}}, {{fieldType.adapter.name}}{{/if}}); 71 | {{/if}} 72 | {{/each}} 73 | {{/dedent-by}} 74 | 75 | constructor() { 76 | super('{{table.name}}'); 77 | } 78 | } 79 | {{#if colMapping}} 80 | 81 | export type {{colMapping.name}} = { 82 | {{#each fields}} 83 | {{#if fieldType.kind}} 84 | {{name}}: ['{{fieldType.kind}}', {{fieldType.tsType.name}}] 85 | {{else}} 86 | {{name}}: '{{fieldType.dbType.name}}' 87 | {{/if}} 88 | {{/each}} 89 | } 90 | {{/if}} 91 | 92 | {{#if instName}} 93 | export const {{instName}} = new {{className}}(); 94 | {{/if}} 95 | 96 | {{#each rowTypes}} 97 | {{#if isInterface}} 98 | export interface {{name}} extends {{{expr}}} {} 99 | {{else}} 100 | export type {{name}} = {{{expr}}}; 101 | {{/if}} 102 | {{/each}} 103 | {{#each valuesTypes}} 104 | {{#if isInterface}} 105 | export interface {{name}} extends {{{expr}}} {} 106 | {{else}} 107 | export type {{name}} = {{{expr}}}; 108 | {{/if}} 109 | {{/each}} 110 | {{#if colSetName}} 111 | export const {{colSetName}} = extractColumnsFrom({{instName}}); 112 | {{/if}} 113 | {{/trim-trailing-whitespace}} 114 | {{#if repo}} 115 | export type {{className}}Pk = SelectedRow<{{className}}>["{{pkField.name}}"]; 116 | 117 | export class {{repo.className}} { 118 | constructor( 119 | public getConnection: () => DBConnection, 120 | public table: {{className}}{{#if instName}} = {{instName}}{{/if}}, 121 | ) {} 122 | 123 | tableCols = {{#if colSetName}}{{colSetName}}{{else}}extractColumnsFrom(this.table){{/if}}; 124 | {{#if repo.methods.select}} 125 | 126 | select(conn = this.getConnection()) { 127 | return conn.selectFrom(this.table); 128 | } 129 | {{/if}} 130 | {{#if repo.methods.selectWhere}} 131 | 132 | selectWhere(cond: DynamicCondition<{{colMapping.name}}>, conn = this.getConnection()) { 133 | return this.select(conn) 134 | .where(conn.dynamicConditionFor(this.tableCols).withValues(cond)) 135 | .select(this.tableCols) 136 | .executeSelectMany(); 137 | } 138 | {{/if}} 139 | {{#if repo.methods.findAll}} 140 | 141 | {{repo.methods.findAll}}(conn = this.getConnection()) { 142 | return this.select(conn) 143 | .select(this.tableCols) 144 | .executeSelectMany(); 145 | } 146 | {{/if}} 147 | {{#if repo.methods.findOne}} 148 | 149 | {{repo.methods.findOne}}({{pkField.name}}: {{className}}Pk, conn = this.getConnection()) { 150 | return this.select(conn) 151 | .where(this.table.{{pkField.name}}.equals({{pkField.name}})) 152 | .select(this.tableCols) 153 | .executeSelectNoneOrOne(); 154 | } 155 | {{/if}} 156 | {{#if repo.methods.findMany}} 157 | 158 | {{repo.methods.findMany}}({{pkField.name}}List: {{className}}Pk[], conn = this.getConnection()) { 159 | return this.select(conn) 160 | .where(this.table.{{pkField.name}}.in({{pkField.name}}List)) 161 | .select(this.tableCols) 162 | .executeSelectMany(); 163 | } 164 | {{/if}} 165 | {{#if repo.methods.insert}} 166 | 167 | insert(conn = this.getConnection()) { 168 | return conn.insertInto(this.table); 169 | } 170 | {{/if}} 171 | {{#if repo.methods.insertOne}} 172 | 173 | {{repo.methods.insertOne}}(row: {{rowTypes.insertable.name}}, conn = this.getConnection()) { 174 | return this.insert(conn) 175 | .set(row) 176 | .returning(this.tableCols) 177 | .executeInsertOne(); 178 | } 179 | {{/if}} 180 | {{#if repo.methods.insertMany}} 181 | 182 | {{repo.methods.insertMany}}(rows: {{rowTypes.insertable.name}}[], conn = this.getConnection()) { 183 | return this.insert(conn) 184 | .values(rows) 185 | .returning(this.tableCols) 186 | .executeInsertMany(); 187 | } 188 | {{/if}} 189 | {{#if repo.methods.update}} 190 | 191 | update(conn = this.getConnection()) { 192 | return conn.update(this.table); 193 | } 194 | {{/if}} 195 | {{#if repo.methods.updateOne}} 196 | 197 | {{repo.methods.updateOne}}({{pkField.name}}: {{className}}Pk, update: {{rowTypes.updatable.name}}, conn = this.getConnection()) { 198 | return this.update(conn) 199 | .set(update) 200 | .where(this.table.{{pkField.name}}.equals({{pkField.name}})) 201 | .returning(this.tableCols) 202 | .executeUpdateOne(); 203 | } 204 | {{/if}} 205 | {{#if repo.methods.updateMany}} 206 | 207 | {{repo.methods.updateMany}}({{pkField.name}}List: {{className}}Pk[], update: {{rowTypes.updatable.name}}, conn = this.getConnection()) { 208 | return this.update(conn) 209 | .set(update) 210 | .where(this.table.{{pkField.name}}.in({{pkField.name}}List)) 211 | .returning(this.tableCols) 212 | .executeUpdateMany(); 213 | } 214 | {{/if}} 215 | {{#if repo.methods.delete}} 216 | 217 | delete(conn = this.getConnection()) { 218 | return conn.deleteFrom(this.table); 219 | } 220 | {{/if}} 221 | {{#if repo.methods.deleteOne}} 222 | 223 | {{repo.methods.deleteOne}}({{pkField.name}}: {{className}}Pk, conn = this.getConnection()) { 224 | return this.delete(conn) 225 | .where(this.table.{{pkField.name}}.equals({{pkField.name}})) 226 | .returning(this.tableCols) 227 | .executeDeleteOne(); 228 | } 229 | {{/if}} 230 | {{#if repo.methods.deleteMany}} 231 | 232 | {{repo.methods.deleteMany}}({{pkField.name}}List: {{className}}Pk[], conn = this.getConnection()) { 233 | return this.delete(conn) 234 | .where(this.table.{{pkField.name}}.in({{pkField.name}}List)) 235 | .returning(this.tableCols) 236 | .executeDeleteMany(); 237 | } 238 | {{/if}} 239 | } 240 | {{/if}} 241 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/allows-customizing-naming.expected.txt: -------------------------------------------------------------------------------- 1 | // TCPAuthorsTCS.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { Table } from "ts-sql-query/Table"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import { 11 | InsertableRow, 12 | UpdatableRow, 13 | SelectedRow, 14 | InsertableValues, 15 | UpdatableValues, 16 | SelectedValues, 17 | } from "ts-sql-query/extras/types"; 18 | import { 19 | extractColumnsFrom, 20 | } from "ts-sql-query/extras/utils"; 21 | 22 | class TCPAuthorsTCS extends Table { 23 | id = this.primaryKey('id', 'int'); 24 | name = this.optionalColumn('name', 'string'); 25 | dob = this.optionalColumn('dob', 'localDate'); 26 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 27 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 28 | 29 | constructor() { 30 | super('authors'); 31 | } 32 | } 33 | 34 | export const TIPAuthorsTIS = new TCPAuthorsTCS(); 35 | 36 | export type SRPAuthorsSRS = SelectedRow; 37 | export type IRPAuthorsIRS = InsertableRow; 38 | export type URPAuthorsURS = UpdatableRow; 39 | export type SVPAuthorsSVS = SelectedValues; 40 | export type IVPAuthorsIVS = InsertableValues; 41 | export type UVPAuthorsUVS = UpdatableValues; 42 | export const TCsPAuthorsTCsS = extractColumnsFrom(TIPAuthorsTIS); 43 | 44 | 45 | // TCPBooksTCS.ts : 46 | /** 47 | * DO NOT EDIT: 48 | * 49 | * This file has been auto-generated from database schema using ts-sql-codegen. 50 | * Any changes will be overwritten. 51 | */ 52 | import { Table } from "ts-sql-query/Table"; 53 | import type { DBConnection } from "../helpers/connection-source"; 54 | import { 55 | InsertableRow, 56 | UpdatableRow, 57 | SelectedRow, 58 | InsertableValues, 59 | UpdatableValues, 60 | SelectedValues, 61 | } from "ts-sql-query/extras/types"; 62 | import { 63 | extractColumnsFrom, 64 | } from "ts-sql-query/extras/utils"; 65 | import { 66 | Genre, 67 | } from "../helpers/types"; 68 | 69 | class TCPBooksTCS extends Table { 70 | id = this.autogeneratedPrimaryKey('id', 'uuid'); 71 | name = this.column('name', 'string'); 72 | authorId = this.column('author_id', 'int'); 73 | releasedAt = this.optionalColumn('released_at', 'localDate'); 74 | timeToRead = this.optionalColumn('time_to_read', 'int'); 75 | genre = this.optionalColumn('genre', 'enum', 'genre'); 76 | /** 77 | * Sample weight as provided by distributor 78 | */ 79 | weightGrams = this.optionalColumn('weight_grams', 'double'); 80 | 81 | constructor() { 82 | super('books'); 83 | } 84 | } 85 | 86 | export const TIPBooksTIS = new TCPBooksTCS(); 87 | 88 | export type SRPBooksSRS = SelectedRow; 89 | export type IRPBooksIRS = InsertableRow; 90 | export type URPBooksURS = UpdatableRow; 91 | export type SVPBooksSVS = SelectedValues; 92 | export type IVPBooksIVS = InsertableValues; 93 | export type UVPBooksUVS = UpdatableValues; 94 | export const TCsPBooksTCsS = extractColumnsFrom(TIPBooksTIS); 95 | 96 | 97 | // TCPChaptersTCS.ts : 98 | /** 99 | * DO NOT EDIT: 100 | * 101 | * This file has been auto-generated from database schema using ts-sql-codegen. 102 | * Any changes will be overwritten. 103 | */ 104 | import { Table } from "ts-sql-query/Table"; 105 | import type { DBConnection } from "../helpers/connection-source"; 106 | import { 107 | InsertableRow, 108 | UpdatableRow, 109 | SelectedRow, 110 | InsertableValues, 111 | UpdatableValues, 112 | SelectedValues, 113 | } from "ts-sql-query/extras/types"; 114 | import { 115 | extractColumnsFrom, 116 | } from "ts-sql-query/extras/utils"; 117 | import { 118 | ChapterMetadataAdapter, 119 | } from "../helpers/adapters"; 120 | import { 121 | ChapterMetadata, 122 | } from "../helpers/types"; 123 | 124 | /** 125 | * Chapters information is only available for books with parseable metadata; Consumers should not assume completeness. 126 | */ 127 | class TCPChaptersTCS extends Table { 128 | id = this.autogeneratedPrimaryKey('id', 'int'); 129 | name = this.column('name', 'string'); 130 | bookId = this.column('book_id', 'uuid'); 131 | metadata = this.optionalColumn('metadata', 'custom', 'jsonb', ChapterMetadataAdapter); 132 | title = this.optionalColumn('title', 'string'); 133 | description = this.optionalColumn('description', 'string'); 134 | 135 | constructor() { 136 | super('chapters'); 137 | } 138 | } 139 | 140 | export const TIPChaptersTIS = new TCPChaptersTCS(); 141 | 142 | export type SRPChaptersSRS = SelectedRow; 143 | export type IRPChaptersIRS = InsertableRow; 144 | export type URPChaptersURS = UpdatableRow; 145 | export type SVPChaptersSVS = SelectedValues; 146 | export type IVPChaptersIVS = InsertableValues; 147 | export type UVPChaptersUVS = UpdatableValues; 148 | export const TCsPChaptersTCsS = extractColumnsFrom(TIPChaptersTIS); 149 | 150 | 151 | // VCPAuthorBooksVCS.ts : 152 | /** 153 | * DO NOT EDIT: 154 | * 155 | * This file has been auto-generated from database schema using ts-sql-codegen. 156 | * Any changes will be overwritten. 157 | */ 158 | import { View } from "ts-sql-query/View"; 159 | import type { DBConnection } from "../helpers/connection-source"; 160 | import { 161 | SelectedRow, 162 | SelectedValues, 163 | } from "ts-sql-query/extras/types"; 164 | import { 165 | extractColumnsFrom, 166 | } from "ts-sql-query/extras/utils"; 167 | import { 168 | Genre, 169 | } from "../helpers/types"; 170 | 171 | class VCPAuthorBooksVCS extends View { 172 | id = this.optionalColumn('id', 'uuid'); 173 | name = this.optionalColumn('name', 'string'); 174 | authorId = this.optionalColumn('author_id', 'int'); 175 | releasedAt = this.optionalColumn('released_at', 'localDate'); 176 | timeToRead = this.optionalColumn('time_to_read', 'int'); 177 | genre = this.optionalColumn('genre', 'enum', 'genre'); 178 | weightGrams = this.optionalColumn('weight_grams', 'double'); 179 | authorName = this.optionalColumn('author_name', 'string'); 180 | 181 | constructor() { 182 | super('author_books'); 183 | } 184 | } 185 | 186 | export const VIPAuthorBooksVIS = new VCPAuthorBooksVCS(); 187 | 188 | export type SRPAuthorBooksSRS = SelectedRow; 189 | export type SVPAuthorBooksSVS = SelectedValues; 190 | export const VCsPAuthorBooksVCsS = extractColumnsFrom(VIPAuthorBooksVIS); 191 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/allows-exporting-crud-repositories.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import { 11 | SelectedRow, 12 | } from "ts-sql-query/extras/types"; 13 | import { 14 | Genre, 15 | } from "../helpers/types"; 16 | 17 | export class AuthorBooksTable extends View { 18 | id = this.optionalColumn('id', 'uuid'); 19 | name = this.optionalColumn('name', 'string'); 20 | authorId = this.optionalColumn('author_id', 'int'); 21 | releasedAt = this.optionalColumn('released_at', 'localDate'); 22 | timeToRead = this.optionalColumn('time_to_read', 'int'); 23 | genre = this.optionalColumn('genre', 'enum', 'genre'); 24 | weightGrams = this.optionalColumn('weight_grams', 'double'); 25 | authorName = this.optionalColumn('author_name', 'string'); 26 | 27 | constructor() { 28 | super('author_books'); 29 | } 30 | } 31 | 32 | 33 | export type AuthorBooksSRow = SelectedRow; 34 | 35 | 36 | // AuthorsTable.ts : 37 | /** 38 | * DO NOT EDIT: 39 | * 40 | * This file has been auto-generated from database schema using ts-sql-codegen. 41 | * Any changes will be overwritten. 42 | */ 43 | import { Table } from "ts-sql-query/Table"; 44 | import type { DBConnection } from "../helpers/connection-source"; 45 | import { 46 | InsertableRow, 47 | UpdatableRow, 48 | SelectedRow, 49 | } from "ts-sql-query/extras/types"; 50 | import { 51 | extractColumnsFrom, 52 | } from "ts-sql-query/extras/utils"; 53 | import { 54 | DynamicCondition, 55 | } from "ts-sql-query/expressions/dynamicConditionUsingFilters"; 56 | 57 | export class AuthorsTable extends Table { 58 | id = this.primaryKey('id', 'int'); 59 | name = this.optionalColumn('name', 'string'); 60 | dob = this.optionalColumn('dob', 'localDate'); 61 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 62 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 63 | 64 | constructor() { 65 | super('authors'); 66 | } 67 | } 68 | 69 | export type AuthorsCols = { 70 | id: 'int' 71 | name: 'string' 72 | dob: 'localDate' 73 | createdAt: 'localDateTime' 74 | updatedAt: 'localDateTime' 75 | } 76 | 77 | 78 | export type AuthorsSRow = SelectedRow; 79 | export type AuthorsIRow = InsertableRow; 80 | export type AuthorsURow = UpdatableRow; 81 | export type AuthorsTablePk = SelectedRow["id"]; 82 | 83 | export class AuthorsCrudRepo { 84 | constructor( 85 | public getConnection: () => DBConnection, 86 | public table: AuthorsTable, 87 | ) {} 88 | 89 | tableCols = extractColumnsFrom(this.table); 90 | 91 | select(conn = this.getConnection()) { 92 | return conn.selectFrom(this.table); 93 | } 94 | 95 | selectWhere(cond: DynamicCondition, conn = this.getConnection()) { 96 | return this.select(conn) 97 | .where(conn.dynamicConditionFor(this.tableCols).withValues(cond)) 98 | .select(this.tableCols) 99 | .executeSelectMany(); 100 | } 101 | 102 | findAll(conn = this.getConnection()) { 103 | return this.select(conn) 104 | .select(this.tableCols) 105 | .executeSelectMany(); 106 | } 107 | 108 | findOneById(id: AuthorsTablePk, conn = this.getConnection()) { 109 | return this.select(conn) 110 | .where(this.table.id.equals(id)) 111 | .select(this.tableCols) 112 | .executeSelectNoneOrOne(); 113 | } 114 | 115 | findManyById(idList: AuthorsTablePk[], conn = this.getConnection()) { 116 | return this.select(conn) 117 | .where(this.table.id.in(idList)) 118 | .select(this.tableCols) 119 | .executeSelectMany(); 120 | } 121 | 122 | insert(conn = this.getConnection()) { 123 | return conn.insertInto(this.table); 124 | } 125 | 126 | insertOne(row: AuthorsIRow, conn = this.getConnection()) { 127 | return this.insert(conn) 128 | .set(row) 129 | .returning(this.tableCols) 130 | .executeInsertOne(); 131 | } 132 | 133 | insertMany(rows: AuthorsIRow[], conn = this.getConnection()) { 134 | return this.insert(conn) 135 | .values(rows) 136 | .returning(this.tableCols) 137 | .executeInsertMany(); 138 | } 139 | 140 | update(conn = this.getConnection()) { 141 | return conn.update(this.table); 142 | } 143 | 144 | updateOneById(id: AuthorsTablePk, update: AuthorsURow, conn = this.getConnection()) { 145 | return this.update(conn) 146 | .set(update) 147 | .where(this.table.id.equals(id)) 148 | .returning(this.tableCols) 149 | .executeUpdateOne(); 150 | } 151 | 152 | updateManyById(idList: AuthorsTablePk[], update: AuthorsURow, conn = this.getConnection()) { 153 | return this.update(conn) 154 | .set(update) 155 | .where(this.table.id.in(idList)) 156 | .returning(this.tableCols) 157 | .executeUpdateMany(); 158 | } 159 | 160 | delete(conn = this.getConnection()) { 161 | return conn.deleteFrom(this.table); 162 | } 163 | 164 | deleteOneById(id: AuthorsTablePk, conn = this.getConnection()) { 165 | return this.delete(conn) 166 | .where(this.table.id.equals(id)) 167 | .returning(this.tableCols) 168 | .executeDeleteOne(); 169 | } 170 | 171 | deleteManyById(idList: AuthorsTablePk[], conn = this.getConnection()) { 172 | return this.delete(conn) 173 | .where(this.table.id.in(idList)) 174 | .returning(this.tableCols) 175 | .executeDeleteMany(); 176 | } 177 | } 178 | 179 | 180 | // BooksTable.ts : 181 | /** 182 | * DO NOT EDIT: 183 | * 184 | * This file has been auto-generated from database schema using ts-sql-codegen. 185 | * Any changes will be overwritten. 186 | */ 187 | import { Table } from "ts-sql-query/Table"; 188 | import type { DBConnection } from "../helpers/connection-source"; 189 | import { 190 | InsertableRow, 191 | UpdatableRow, 192 | SelectedRow, 193 | } from "ts-sql-query/extras/types"; 194 | import { 195 | extractColumnsFrom, 196 | } from "ts-sql-query/extras/utils"; 197 | import { 198 | Genre, 199 | } from "../helpers/types"; 200 | import { 201 | DynamicCondition, 202 | } from "ts-sql-query/expressions/dynamicConditionUsingFilters"; 203 | 204 | export class BooksTable extends Table { 205 | id = this.autogeneratedPrimaryKey('id', 'uuid'); 206 | name = this.column('name', 'string'); 207 | authorId = this.column('author_id', 'int'); 208 | releasedAt = this.optionalColumn('released_at', 'localDate'); 209 | timeToRead = this.optionalColumn('time_to_read', 'int'); 210 | genre = this.optionalColumn('genre', 'enum', 'genre'); 211 | /** 212 | * Sample weight as provided by distributor 213 | */ 214 | weightGrams = this.optionalColumn('weight_grams', 'double'); 215 | 216 | constructor() { 217 | super('books'); 218 | } 219 | } 220 | 221 | export type BooksCols = { 222 | id: 'uuid' 223 | name: 'string' 224 | authorId: 'int' 225 | releasedAt: 'localDate' 226 | timeToRead: 'int' 227 | genre: ['enum', Genre] 228 | weightGrams: 'double' 229 | } 230 | 231 | 232 | export type BooksSRow = SelectedRow; 233 | export type BooksIRow = InsertableRow; 234 | export type BooksURow = UpdatableRow; 235 | export type BooksTablePk = SelectedRow["id"]; 236 | 237 | export class BooksCrudRepo { 238 | constructor( 239 | public getConnection: () => DBConnection, 240 | public table: BooksTable, 241 | ) {} 242 | 243 | tableCols = extractColumnsFrom(this.table); 244 | 245 | select(conn = this.getConnection()) { 246 | return conn.selectFrom(this.table); 247 | } 248 | 249 | selectWhere(cond: DynamicCondition, conn = this.getConnection()) { 250 | return this.select(conn) 251 | .where(conn.dynamicConditionFor(this.tableCols).withValues(cond)) 252 | .select(this.tableCols) 253 | .executeSelectMany(); 254 | } 255 | 256 | findAll(conn = this.getConnection()) { 257 | return this.select(conn) 258 | .select(this.tableCols) 259 | .executeSelectMany(); 260 | } 261 | 262 | findOneById(id: BooksTablePk, conn = this.getConnection()) { 263 | return this.select(conn) 264 | .where(this.table.id.equals(id)) 265 | .select(this.tableCols) 266 | .executeSelectNoneOrOne(); 267 | } 268 | 269 | findManyById(idList: BooksTablePk[], conn = this.getConnection()) { 270 | return this.select(conn) 271 | .where(this.table.id.in(idList)) 272 | .select(this.tableCols) 273 | .executeSelectMany(); 274 | } 275 | 276 | insert(conn = this.getConnection()) { 277 | return conn.insertInto(this.table); 278 | } 279 | 280 | insertOne(row: BooksIRow, conn = this.getConnection()) { 281 | return this.insert(conn) 282 | .set(row) 283 | .returning(this.tableCols) 284 | .executeInsertOne(); 285 | } 286 | 287 | insertMany(rows: BooksIRow[], conn = this.getConnection()) { 288 | return this.insert(conn) 289 | .values(rows) 290 | .returning(this.tableCols) 291 | .executeInsertMany(); 292 | } 293 | 294 | update(conn = this.getConnection()) { 295 | return conn.update(this.table); 296 | } 297 | 298 | updateOneById(id: BooksTablePk, update: BooksURow, conn = this.getConnection()) { 299 | return this.update(conn) 300 | .set(update) 301 | .where(this.table.id.equals(id)) 302 | .returning(this.tableCols) 303 | .executeUpdateOne(); 304 | } 305 | 306 | updateManyById(idList: BooksTablePk[], update: BooksURow, conn = this.getConnection()) { 307 | return this.update(conn) 308 | .set(update) 309 | .where(this.table.id.in(idList)) 310 | .returning(this.tableCols) 311 | .executeUpdateMany(); 312 | } 313 | 314 | delete(conn = this.getConnection()) { 315 | return conn.deleteFrom(this.table); 316 | } 317 | 318 | deleteOneById(id: BooksTablePk, conn = this.getConnection()) { 319 | return this.delete(conn) 320 | .where(this.table.id.equals(id)) 321 | .returning(this.tableCols) 322 | .executeDeleteOne(); 323 | } 324 | 325 | deleteManyById(idList: BooksTablePk[], conn = this.getConnection()) { 326 | return this.delete(conn) 327 | .where(this.table.id.in(idList)) 328 | .returning(this.tableCols) 329 | .executeDeleteMany(); 330 | } 331 | } 332 | 333 | 334 | // ChaptersTable.ts : 335 | /** 336 | * DO NOT EDIT: 337 | * 338 | * This file has been auto-generated from database schema using ts-sql-codegen. 339 | * Any changes will be overwritten. 340 | */ 341 | import { Table } from "ts-sql-query/Table"; 342 | import type { DBConnection } from "../helpers/connection-source"; 343 | import { 344 | InsertableRow, 345 | UpdatableRow, 346 | SelectedRow, 347 | } from "ts-sql-query/extras/types"; 348 | import { 349 | extractColumnsFrom, 350 | } from "ts-sql-query/extras/utils"; 351 | import { 352 | ChapterMetadataAdapter, 353 | } from "../helpers/adapters"; 354 | import { 355 | ChapterMetadata, 356 | } from "../helpers/types"; 357 | import { 358 | DynamicCondition, 359 | } from "ts-sql-query/expressions/dynamicConditionUsingFilters"; 360 | 361 | /** 362 | * Chapters information is only available for books with parseable metadata; Consumers should not assume completeness. 363 | */ 364 | export class ChaptersTable extends Table { 365 | id = this.autogeneratedPrimaryKey('id', 'int'); 366 | name = this.column('name', 'string'); 367 | bookId = this.column('book_id', 'uuid'); 368 | metadata = this.optionalColumn('metadata', 'custom', 'jsonb', ChapterMetadataAdapter); 369 | title = this.optionalColumn('title', 'string'); 370 | description = this.optionalColumn('description', 'string'); 371 | 372 | constructor() { 373 | super('chapters'); 374 | } 375 | } 376 | 377 | export type ChaptersCols = { 378 | id: 'int' 379 | name: 'string' 380 | bookId: 'uuid' 381 | metadata: ['custom', ChapterMetadata] 382 | title: 'string' 383 | description: 'string' 384 | } 385 | 386 | 387 | export type ChaptersSRow = SelectedRow; 388 | export type ChaptersIRow = InsertableRow; 389 | export type ChaptersURow = UpdatableRow; 390 | export type ChaptersTablePk = SelectedRow["id"]; 391 | 392 | export class ChaptersCrudRepo { 393 | constructor( 394 | public getConnection: () => DBConnection, 395 | public table: ChaptersTable, 396 | ) {} 397 | 398 | tableCols = extractColumnsFrom(this.table); 399 | 400 | select(conn = this.getConnection()) { 401 | return conn.selectFrom(this.table); 402 | } 403 | 404 | selectWhere(cond: DynamicCondition, conn = this.getConnection()) { 405 | return this.select(conn) 406 | .where(conn.dynamicConditionFor(this.tableCols).withValues(cond)) 407 | .select(this.tableCols) 408 | .executeSelectMany(); 409 | } 410 | 411 | findAll(conn = this.getConnection()) { 412 | return this.select(conn) 413 | .select(this.tableCols) 414 | .executeSelectMany(); 415 | } 416 | 417 | findOneById(id: ChaptersTablePk, conn = this.getConnection()) { 418 | return this.select(conn) 419 | .where(this.table.id.equals(id)) 420 | .select(this.tableCols) 421 | .executeSelectNoneOrOne(); 422 | } 423 | 424 | findManyById(idList: ChaptersTablePk[], conn = this.getConnection()) { 425 | return this.select(conn) 426 | .where(this.table.id.in(idList)) 427 | .select(this.tableCols) 428 | .executeSelectMany(); 429 | } 430 | 431 | insert(conn = this.getConnection()) { 432 | return conn.insertInto(this.table); 433 | } 434 | 435 | insertOne(row: ChaptersIRow, conn = this.getConnection()) { 436 | return this.insert(conn) 437 | .set(row) 438 | .returning(this.tableCols) 439 | .executeInsertOne(); 440 | } 441 | 442 | insertMany(rows: ChaptersIRow[], conn = this.getConnection()) { 443 | return this.insert(conn) 444 | .values(rows) 445 | .returning(this.tableCols) 446 | .executeInsertMany(); 447 | } 448 | 449 | update(conn = this.getConnection()) { 450 | return conn.update(this.table); 451 | } 452 | 453 | updateOneById(id: ChaptersTablePk, update: ChaptersURow, conn = this.getConnection()) { 454 | return this.update(conn) 455 | .set(update) 456 | .where(this.table.id.equals(id)) 457 | .returning(this.tableCols) 458 | .executeUpdateOne(); 459 | } 460 | 461 | updateManyById(idList: ChaptersTablePk[], update: ChaptersURow, conn = this.getConnection()) { 462 | return this.update(conn) 463 | .set(update) 464 | .where(this.table.id.in(idList)) 465 | .returning(this.tableCols) 466 | .executeUpdateMany(); 467 | } 468 | 469 | delete(conn = this.getConnection()) { 470 | return conn.deleteFrom(this.table); 471 | } 472 | 473 | deleteOneById(id: ChaptersTablePk, conn = this.getConnection()) { 474 | return this.delete(conn) 475 | .where(this.table.id.equals(id)) 476 | .returning(this.tableCols) 477 | .executeDeleteOne(); 478 | } 479 | 480 | deleteManyById(idList: ChaptersTablePk[], conn = this.getConnection()) { 481 | return this.delete(conn) 482 | .where(this.table.id.in(idList)) 483 | .returning(this.tableCols) 484 | .executeDeleteMany(); 485 | } 486 | } 487 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/allows-exporting-instances.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import { 11 | Genre, 12 | } from "../helpers/types"; 13 | 14 | class AuthorBooksTable extends View { 15 | id = this.optionalColumn('id', 'uuid'); 16 | name = this.optionalColumn('name', 'string'); 17 | authorId = this.optionalColumn('author_id', 'int'); 18 | releasedAt = this.optionalColumn('released_at', 'localDate'); 19 | timeToRead = this.optionalColumn('time_to_read', 'int'); 20 | genre = this.optionalColumn('genre', 'enum', 'genre'); 21 | weightGrams = this.optionalColumn('weight_grams', 'double'); 22 | authorName = this.optionalColumn('author_name', 'string'); 23 | 24 | constructor() { 25 | super('author_books'); 26 | } 27 | } 28 | 29 | export const tAuthorBooks = new AuthorBooksTable(); 30 | 31 | 32 | 33 | // AuthorsTable.ts : 34 | /** 35 | * DO NOT EDIT: 36 | * 37 | * This file has been auto-generated from database schema using ts-sql-codegen. 38 | * Any changes will be overwritten. 39 | */ 40 | import { Table } from "ts-sql-query/Table"; 41 | import type { DBConnection } from "../helpers/connection-source"; 42 | 43 | class AuthorsTable extends Table { 44 | id = this.primaryKey('id', 'int'); 45 | name = this.optionalColumn('name', 'string'); 46 | dob = this.optionalColumn('dob', 'localDate'); 47 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 48 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 49 | 50 | constructor() { 51 | super('authors'); 52 | } 53 | } 54 | 55 | export const tAuthors = new AuthorsTable(); 56 | 57 | 58 | 59 | // BooksTable.ts : 60 | /** 61 | * DO NOT EDIT: 62 | * 63 | * This file has been auto-generated from database schema using ts-sql-codegen. 64 | * Any changes will be overwritten. 65 | */ 66 | import { Table } from "ts-sql-query/Table"; 67 | import type { DBConnection } from "../helpers/connection-source"; 68 | import { 69 | Genre, 70 | } from "../helpers/types"; 71 | 72 | class BooksTable extends Table { 73 | id = this.autogeneratedPrimaryKey('id', 'uuid'); 74 | name = this.column('name', 'string'); 75 | authorId = this.column('author_id', 'int'); 76 | releasedAt = this.optionalColumn('released_at', 'localDate'); 77 | timeToRead = this.optionalColumn('time_to_read', 'int'); 78 | genre = this.optionalColumn('genre', 'enum', 'genre'); 79 | /** 80 | * Sample weight as provided by distributor 81 | */ 82 | weightGrams = this.optionalColumn('weight_grams', 'double'); 83 | 84 | constructor() { 85 | super('books'); 86 | } 87 | } 88 | 89 | export const tBooks = new BooksTable(); 90 | 91 | 92 | 93 | // ChaptersTable.ts : 94 | /** 95 | * DO NOT EDIT: 96 | * 97 | * This file has been auto-generated from database schema using ts-sql-codegen. 98 | * Any changes will be overwritten. 99 | */ 100 | import { Table } from "ts-sql-query/Table"; 101 | import type { DBConnection } from "../helpers/connection-source"; 102 | import { 103 | ChapterMetadataAdapter, 104 | } from "../helpers/adapters"; 105 | import { 106 | ChapterMetadata, 107 | } from "../helpers/types"; 108 | 109 | /** 110 | * Chapters information is only available for books with parseable metadata; Consumers should not assume completeness. 111 | */ 112 | class ChaptersTable extends Table { 113 | id = this.autogeneratedPrimaryKey('id', 'int'); 114 | name = this.column('name', 'string'); 115 | bookId = this.column('book_id', 'uuid'); 116 | metadata = this.optionalColumn('metadata', 'custom', 'jsonb', ChapterMetadataAdapter); 117 | title = this.optionalColumn('title', 'string'); 118 | description = this.optionalColumn('description', 'string'); 119 | 120 | constructor() { 121 | super('chapters'); 122 | } 123 | } 124 | 125 | export const tChapters = new ChaptersTable(); 126 | 127 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/allows-exporting-row-types-as-interface.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorsTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { Table } from "ts-sql-query/Table"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import { 11 | InsertableRow, 12 | UpdatableRow, 13 | SelectedRow, 14 | } from "ts-sql-query/extras/types"; 15 | 16 | export class AuthorsTable extends Table { 17 | id = this.primaryKey('id', 'int'); 18 | name = this.optionalColumn('name', 'string'); 19 | dob = this.optionalColumn('dob', 'localDate'); 20 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 21 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 22 | 23 | constructor() { 24 | super('authors'); 25 | } 26 | } 27 | 28 | 29 | export interface AuthorsSRow extends SelectedRow {} 30 | export interface AuthorsIRow extends InsertableRow {} 31 | export interface AuthorsURow extends UpdatableRow {} 32 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/allows-exporting-row-types.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorsTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { Table } from "ts-sql-query/Table"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import { 11 | InsertableRow, 12 | UpdatableRow, 13 | SelectedRow, 14 | } from "ts-sql-query/extras/types"; 15 | 16 | export class AuthorsTable extends Table { 17 | id = this.primaryKey('id', 'int'); 18 | name = this.optionalColumn('name', 'string'); 19 | dob = this.optionalColumn('dob', 'localDate'); 20 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 21 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 22 | 23 | constructor() { 24 | super('authors'); 25 | } 26 | } 27 | 28 | 29 | export type AuthorsSRow = SelectedRow; 30 | export type AuthorsIRow = InsertableRow; 31 | export type AuthorsURow = UpdatableRow; 32 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/allows-exporting-value-types-as-interface.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorsTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { Table } from "ts-sql-query/Table"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import { 11 | InsertableValues, 12 | UpdatableValues, 13 | SelectedValues, 14 | } from "ts-sql-query/extras/types"; 15 | 16 | export class AuthorsTable extends Table { 17 | id = this.primaryKey('id', 'int'); 18 | name = this.optionalColumn('name', 'string'); 19 | dob = this.optionalColumn('dob', 'localDate'); 20 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 21 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 22 | 23 | constructor() { 24 | super('authors'); 25 | } 26 | } 27 | 28 | 29 | export interface Authors extends SelectedValues {} 30 | export interface InsertableAuthors extends InsertableValues {} 31 | export interface UpdatableAuthors extends UpdatableValues {} 32 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/allows-exporting-value-types.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorsTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { Table } from "ts-sql-query/Table"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import { 11 | InsertableValues, 12 | UpdatableValues, 13 | SelectedValues, 14 | } from "ts-sql-query/extras/types"; 15 | 16 | export class AuthorsTable extends Table { 17 | id = this.primaryKey('id', 'int'); 18 | name = this.optionalColumn('name', 'string'); 19 | dob = this.optionalColumn('dob', 'localDate'); 20 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 21 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 22 | 23 | constructor() { 24 | super('authors'); 25 | } 26 | } 27 | 28 | 29 | export type Authors = SelectedValues; 30 | export type InsertableAuthors = InsertableValues; 31 | export type UpdatableAuthors = UpdatableValues; 32 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/allows-non-relative-and-default-import-paths-with-db-type-name.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "../helpers/types"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'enum', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | 30 | 31 | // AuthorsTable.ts : 32 | /** 33 | * DO NOT EDIT: 34 | * 35 | * This file has been auto-generated from database schema using ts-sql-codegen. 36 | * Any changes will be overwritten. 37 | */ 38 | import { Table } from "ts-sql-query/Table"; 39 | import type { DBConnection } from "../helpers/connection-source"; 40 | 41 | class AuthorsTable extends Table { 42 | id = this.primaryKey('id', 'int'); 43 | name = this.optionalColumn('name', 'string'); 44 | dob = this.optionalColumn('dob', 'localDate'); 45 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 46 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 47 | 48 | constructor() { 49 | super('authors'); 50 | } 51 | } 52 | 53 | export const tAuthors = new AuthorsTable(); 54 | 55 | 56 | 57 | // BooksTable.ts : 58 | /** 59 | * DO NOT EDIT: 60 | * 61 | * This file has been auto-generated from database schema using ts-sql-codegen. 62 | * Any changes will be overwritten. 63 | */ 64 | import { Table } from "ts-sql-query/Table"; 65 | import type { DBConnection } from "../helpers/connection-source"; 66 | import Genre from "../helpers/types"; 67 | 68 | class BooksTable extends Table { 69 | id = this.autogeneratedPrimaryKey('id', 'uuid'); 70 | name = this.column('name', 'string'); 71 | authorId = this.column('author_id', 'int'); 72 | releasedAt = this.optionalColumn('released_at', 'localDate'); 73 | timeToRead = this.optionalColumn('time_to_read', 'int'); 74 | genre = this.optionalColumn('genre', 'enum', 'genre'); 75 | /** 76 | * Sample weight as provided by distributor 77 | */ 78 | weightGrams = this.optionalColumn('weight_grams', 'double'); 79 | 80 | constructor() { 81 | super('books'); 82 | } 83 | } 84 | 85 | export const tBooks = new BooksTable(); 86 | 87 | 88 | 89 | // ChaptersTable.ts : 90 | /** 91 | * DO NOT EDIT: 92 | * 93 | * This file has been auto-generated from database schema using ts-sql-codegen. 94 | * Any changes will be overwritten. 95 | */ 96 | import { Table } from "ts-sql-query/Table"; 97 | import type { DBConnection } from "../helpers/connection-source"; 98 | import { 99 | ChapterMetadataAdapter, 100 | } from "some-other-lib"; 101 | import ChapterMetadata from "some-lib/ChapterMetadata"; 102 | 103 | /** 104 | * Chapters information is only available for books with parseable metadata; Consumers should not assume completeness. 105 | */ 106 | class ChaptersTable extends Table { 107 | id = this.autogeneratedPrimaryKey('id', 'int'); 108 | name = this.column('name', 'string'); 109 | bookId = this.column('book_id', 'uuid'); 110 | metadata = this.optionalColumn('metadata', 'custom', 'jsonb', ChapterMetadataAdapter); 111 | title = this.optionalColumn('title', 'string'); 112 | description = this.optionalColumn('description', 'string'); 113 | 114 | constructor() { 115 | super('chapters'); 116 | } 117 | } 118 | 119 | export const tChapters = new ChaptersTable(); 120 | 121 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/allows-non-relative-and-default-import-paths.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "../helpers/types"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'enum', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | 30 | 31 | // AuthorsTable.ts : 32 | /** 33 | * DO NOT EDIT: 34 | * 35 | * This file has been auto-generated from database schema using ts-sql-codegen. 36 | * Any changes will be overwritten. 37 | */ 38 | import { Table } from "ts-sql-query/Table"; 39 | import type { DBConnection } from "../helpers/connection-source"; 40 | 41 | class AuthorsTable extends Table { 42 | id = this.primaryKey('id', 'int'); 43 | name = this.optionalColumn('name', 'string'); 44 | dob = this.optionalColumn('dob', 'localDate'); 45 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 46 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 47 | 48 | constructor() { 49 | super('authors'); 50 | } 51 | } 52 | 53 | export const tAuthors = new AuthorsTable(); 54 | 55 | 56 | 57 | // BooksTable.ts : 58 | /** 59 | * DO NOT EDIT: 60 | * 61 | * This file has been auto-generated from database schema using ts-sql-codegen. 62 | * Any changes will be overwritten. 63 | */ 64 | import { Table } from "ts-sql-query/Table"; 65 | import type { DBConnection } from "../helpers/connection-source"; 66 | import Genre from "../helpers/types"; 67 | 68 | class BooksTable extends Table { 69 | id = this.autogeneratedPrimaryKey('id', 'uuid'); 70 | name = this.column('name', 'string'); 71 | authorId = this.column('author_id', 'int'); 72 | releasedAt = this.optionalColumn('released_at', 'localDate'); 73 | timeToRead = this.optionalColumn('time_to_read', 'int'); 74 | genre = this.optionalColumn('genre', 'enum', 'genre'); 75 | /** 76 | * Sample weight as provided by distributor 77 | */ 78 | weightGrams = this.optionalColumn('weight_grams', 'double'); 79 | 80 | constructor() { 81 | super('books'); 82 | } 83 | } 84 | 85 | export const tBooks = new BooksTable(); 86 | 87 | 88 | 89 | // ChaptersTable.ts : 90 | /** 91 | * DO NOT EDIT: 92 | * 93 | * This file has been auto-generated from database schema using ts-sql-codegen. 94 | * Any changes will be overwritten. 95 | */ 96 | import { Table } from "ts-sql-query/Table"; 97 | import type { DBConnection } from "../helpers/connection-source"; 98 | import { 99 | ChapterMetadataAdapter, 100 | } from "some-other-lib"; 101 | import ChapterMetadata from "some-lib/ChapterMetadata"; 102 | 103 | /** 104 | * Chapters information is only available for books with parseable metadata; Consumers should not assume completeness. 105 | */ 106 | class ChaptersTable extends Table { 107 | id = this.autogeneratedPrimaryKey('id', 'int'); 108 | name = this.column('name', 'string'); 109 | bookId = this.column('book_id', 'uuid'); 110 | metadata = this.optionalColumn('metadata', 'custom', 'jsonb', ChapterMetadataAdapter); 111 | title = this.optionalColumn('title', 'string'); 112 | description = this.optionalColumn('description', 'string'); 113 | 114 | constructor() { 115 | super('chapters'); 116 | } 117 | } 118 | 119 | export const tChapters = new ChaptersTable(); 120 | 121 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/allows-omitting-specific-tables-and-fields.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorsTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { Table } from "ts-sql-query/Table"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | 11 | export class AuthorsTable extends Table { 12 | id = this.primaryKey('id', 'int'); 13 | dob = this.optionalColumn('dob', 'localDate'); 14 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 15 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 16 | 17 | constructor() { 18 | super('authors'); 19 | } 20 | } 21 | 22 | 23 | 24 | 25 | // BooksTable.ts : 26 | /** 27 | * DO NOT EDIT: 28 | * 29 | * This file has been auto-generated from database schema using ts-sql-codegen. 30 | * Any changes will be overwritten. 31 | */ 32 | import { Table } from "ts-sql-query/Table"; 33 | import type { DBConnection } from "../helpers/connection-source"; 34 | import { 35 | Genre, 36 | } from "../helpers/types"; 37 | 38 | export class BooksTable extends Table { 39 | id = this.autogeneratedPrimaryKey('id', 'uuid'); 40 | name = this.column('name', 'string'); 41 | authorId = this.column('author_id', 'int'); 42 | releasedAt = this.optionalColumn('released_at', 'localDate'); 43 | readTime = this.optionalColumn('time_to_read', 'int'); 44 | genre = this.optionalColumn('genre', 'enum', 'genre'); 45 | /** 46 | * Sample weight as provided by distributor 47 | */ 48 | weightGrams = this.optionalColumn('weight_grams', 'double'); 49 | 50 | constructor() { 51 | super('books'); 52 | } 53 | } 54 | 55 | 56 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/allows-wrapping-exported-types.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorsTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { Table } from "ts-sql-query/Table"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import { 11 | InsertableRow, 12 | UpdatableRow, 13 | SelectedRow, 14 | InsertableValues, 15 | UpdatableValues, 16 | SelectedValues, 17 | } from "ts-sql-query/extras/types"; 18 | import { 19 | EnforceAuthorInsertProps, 20 | } from "../type-helpers"; 21 | import { 22 | EnforceUpdateProps, 23 | } from "../type-helpers"; 24 | 25 | export class AuthorsTable extends Table { 26 | id = this.primaryKey('id', 'int'); 27 | name = this.optionalColumn('name', 'string'); 28 | dob = this.optionalColumn('dob', 'localDate'); 29 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 30 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 31 | 32 | constructor() { 33 | super('authors'); 34 | } 35 | } 36 | 37 | 38 | export type AuthorsSRow = SelectedRow; 39 | export type AuthorsIRow = EnforceAuthorInsertProps>; 40 | export type AuthorsURow = EnforceUpdateProps>; 41 | export type Authors = SelectedValues; 42 | export type InsertableAuthors = InsertableValues; 43 | export type UpdatableAuthors = UpdatableValues; 44 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/generates-code-from-schema-with-useQualifiedTablePrefix-false.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import { 11 | Genre, 12 | } from "../helpers/types"; 13 | 14 | export class AuthorBooksTable extends View { 15 | id = this.optionalColumn('id', 'uuid'); 16 | name = this.optionalColumn('name', 'string'); 17 | authorId = this.optionalColumn('author_id', 'int'); 18 | releasedAt = this.optionalColumn('released_at', 'localDate'); 19 | timeToRead = this.optionalColumn('time_to_read', 'int'); 20 | genre = this.optionalColumn('genre', 'enum', 'genre'); 21 | weightGrams = this.optionalColumn('weight_grams', 'double'); 22 | authorName = this.optionalColumn('author_name', 'string'); 23 | 24 | constructor() { 25 | super('author_books'); 26 | } 27 | } 28 | 29 | 30 | 31 | 32 | // AuthorsTable.ts : 33 | /** 34 | * DO NOT EDIT: 35 | * 36 | * This file has been auto-generated from database schema using ts-sql-codegen. 37 | * Any changes will be overwritten. 38 | */ 39 | import { Table } from "ts-sql-query/Table"; 40 | import type { DBConnection } from "../helpers/connection-source"; 41 | 42 | export class AuthorsTable extends Table { 43 | id = this.primaryKey('id', 'int'); 44 | name = this.optionalColumn('name', 'string'); 45 | dob = this.optionalColumn('dob', 'localDate'); 46 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 47 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 48 | 49 | constructor() { 50 | super('authors'); 51 | } 52 | } 53 | 54 | 55 | 56 | 57 | // BooksTable.ts : 58 | /** 59 | * DO NOT EDIT: 60 | * 61 | * This file has been auto-generated from database schema using ts-sql-codegen. 62 | * Any changes will be overwritten. 63 | */ 64 | import { Table } from "ts-sql-query/Table"; 65 | import type { DBConnection } from "../helpers/connection-source"; 66 | import { 67 | Genre, 68 | } from "../helpers/types"; 69 | 70 | export class BooksTable extends Table { 71 | id = this.autogeneratedPrimaryKey('id', 'uuid'); 72 | name = this.column('name', 'string'); 73 | authorId = this.column('author_id', 'int'); 74 | releasedAt = this.optionalColumn('released_at', 'localDate'); 75 | timeToRead = this.optionalColumn('time_to_read', 'int'); 76 | genre = this.optionalColumn('genre', 'enum', 'genre'); 77 | /** 78 | * Sample weight as provided by distributor 79 | */ 80 | weightGrams = this.optionalColumn('weight_grams', 'double'); 81 | 82 | constructor() { 83 | super('books'); 84 | } 85 | } 86 | 87 | 88 | 89 | 90 | // ChaptersTable.ts : 91 | /** 92 | * DO NOT EDIT: 93 | * 94 | * This file has been auto-generated from database schema using ts-sql-codegen. 95 | * Any changes will be overwritten. 96 | */ 97 | import { Table } from "ts-sql-query/Table"; 98 | import type { DBConnection } from "../helpers/connection-source"; 99 | import { 100 | ChapterMetadataAdapter, 101 | } from "../helpers/adapters"; 102 | import { 103 | ChapterMetadata, 104 | } from "../helpers/types"; 105 | 106 | /** 107 | * Chapters information is only available for books with parseable metadata; Consumers should not assume completeness. 108 | */ 109 | export class ChaptersTable extends Table { 110 | id = this.autogeneratedPrimaryKey('id', 'int'); 111 | name = this.column('name', 'string'); 112 | bookId = this.column('book_id', 'uuid'); 113 | metadata = this.optionalColumn('metadata', 'custom', 'jsonb', ChapterMetadataAdapter); 114 | title = this.optionalColumn('title', 'string'); 115 | description = this.optionalColumn('description', 'string'); 116 | 117 | constructor() { 118 | super('chapters'); 119 | } 120 | } 121 | 122 | 123 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/generates-code-from-schema-with-useQualifiedTablePrefix-true.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import { 11 | Genre, 12 | } from "../helpers/types"; 13 | 14 | export class AuthorBooksTable extends View { 15 | id = this.optionalColumn('id', 'uuid'); 16 | name = this.optionalColumn('name', 'string'); 17 | authorId = this.optionalColumn('author_id', 'int'); 18 | releasedAt = this.optionalColumn('released_at', 'localDate'); 19 | timeToRead = this.optionalColumn('time_to_read', 'int'); 20 | genre = this.optionalColumn('genre', 'enum', 'genre'); 21 | weightGrams = this.optionalColumn('weight_grams', 'double'); 22 | authorName = this.optionalColumn('author_name', 'string'); 23 | 24 | constructor() { 25 | super('public.author_books'); 26 | } 27 | } 28 | 29 | 30 | 31 | 32 | // AuthorsTable.ts : 33 | /** 34 | * DO NOT EDIT: 35 | * 36 | * This file has been auto-generated from database schema using ts-sql-codegen. 37 | * Any changes will be overwritten. 38 | */ 39 | import { Table } from "ts-sql-query/Table"; 40 | import type { DBConnection } from "../helpers/connection-source"; 41 | 42 | export class AuthorsTable extends Table { 43 | id = this.primaryKey('id', 'int'); 44 | name = this.optionalColumn('name', 'string'); 45 | dob = this.optionalColumn('dob', 'localDate'); 46 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 47 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 48 | 49 | constructor() { 50 | super('public.authors'); 51 | } 52 | } 53 | 54 | 55 | 56 | 57 | // BooksTable.ts : 58 | /** 59 | * DO NOT EDIT: 60 | * 61 | * This file has been auto-generated from database schema using ts-sql-codegen. 62 | * Any changes will be overwritten. 63 | */ 64 | import { Table } from "ts-sql-query/Table"; 65 | import type { DBConnection } from "../helpers/connection-source"; 66 | import { 67 | Genre, 68 | } from "../helpers/types"; 69 | 70 | export class BooksTable extends Table { 71 | id = this.autogeneratedPrimaryKey('id', 'uuid'); 72 | name = this.column('name', 'string'); 73 | authorId = this.column('author_id', 'int'); 74 | releasedAt = this.optionalColumn('released_at', 'localDate'); 75 | timeToRead = this.optionalColumn('time_to_read', 'int'); 76 | genre = this.optionalColumn('genre', 'enum', 'genre'); 77 | /** 78 | * Sample weight as provided by distributor 79 | */ 80 | weightGrams = this.optionalColumn('weight_grams', 'double'); 81 | 82 | constructor() { 83 | super('public.books'); 84 | } 85 | } 86 | 87 | 88 | 89 | 90 | // ChaptersTable.ts : 91 | /** 92 | * DO NOT EDIT: 93 | * 94 | * This file has been auto-generated from database schema using ts-sql-codegen. 95 | * Any changes will be overwritten. 96 | */ 97 | import { Table } from "ts-sql-query/Table"; 98 | import type { DBConnection } from "../helpers/connection-source"; 99 | import { 100 | ChapterMetadataAdapter, 101 | } from "../helpers/adapters"; 102 | import { 103 | ChapterMetadata, 104 | } from "../helpers/types"; 105 | 106 | /** 107 | * Chapters information is only available for books with parseable metadata; Consumers should not assume completeness. 108 | */ 109 | export class ChaptersTable extends Table { 110 | id = this.autogeneratedPrimaryKey('id', 'int'); 111 | name = this.column('name', 'string'); 112 | bookId = this.column('book_id', 'uuid'); 113 | metadata = this.optionalColumn('metadata', 'custom', 'jsonb', ChapterMetadataAdapter); 114 | title = this.optionalColumn('title', 'string'); 115 | description = this.optionalColumn('description', 'string'); 116 | 117 | constructor() { 118 | super('public.chapters'); 119 | } 120 | } 121 | 122 | 123 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/generates-code-from-schema-with-useQualifiedTablePrefix-undefined.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import { 11 | Genre, 12 | } from "../helpers/types"; 13 | 14 | export class AuthorBooksTable extends View { 15 | id = this.optionalColumn('id', 'uuid'); 16 | name = this.optionalColumn('name', 'string'); 17 | authorId = this.optionalColumn('author_id', 'int'); 18 | releasedAt = this.optionalColumn('released_at', 'localDate'); 19 | timeToRead = this.optionalColumn('time_to_read', 'int'); 20 | genre = this.optionalColumn('genre', 'enum', 'genre'); 21 | weightGrams = this.optionalColumn('weight_grams', 'double'); 22 | authorName = this.optionalColumn('author_name', 'string'); 23 | 24 | constructor() { 25 | super('author_books'); 26 | } 27 | } 28 | 29 | 30 | 31 | 32 | // AuthorsTable.ts : 33 | /** 34 | * DO NOT EDIT: 35 | * 36 | * This file has been auto-generated from database schema using ts-sql-codegen. 37 | * Any changes will be overwritten. 38 | */ 39 | import { Table } from "ts-sql-query/Table"; 40 | import type { DBConnection } from "../helpers/connection-source"; 41 | 42 | export class AuthorsTable extends Table { 43 | id = this.primaryKey('id', 'int'); 44 | name = this.optionalColumn('name', 'string'); 45 | dob = this.optionalColumn('dob', 'localDate'); 46 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 47 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 48 | 49 | constructor() { 50 | super('authors'); 51 | } 52 | } 53 | 54 | 55 | 56 | 57 | // BooksTable.ts : 58 | /** 59 | * DO NOT EDIT: 60 | * 61 | * This file has been auto-generated from database schema using ts-sql-codegen. 62 | * Any changes will be overwritten. 63 | */ 64 | import { Table } from "ts-sql-query/Table"; 65 | import type { DBConnection } from "../helpers/connection-source"; 66 | import { 67 | Genre, 68 | } from "../helpers/types"; 69 | 70 | export class BooksTable extends Table { 71 | id = this.autogeneratedPrimaryKey('id', 'uuid'); 72 | name = this.column('name', 'string'); 73 | authorId = this.column('author_id', 'int'); 74 | releasedAt = this.optionalColumn('released_at', 'localDate'); 75 | timeToRead = this.optionalColumn('time_to_read', 'int'); 76 | genre = this.optionalColumn('genre', 'enum', 'genre'); 77 | /** 78 | * Sample weight as provided by distributor 79 | */ 80 | weightGrams = this.optionalColumn('weight_grams', 'double'); 81 | 82 | constructor() { 83 | super('books'); 84 | } 85 | } 86 | 87 | 88 | 89 | 90 | // ChaptersTable.ts : 91 | /** 92 | * DO NOT EDIT: 93 | * 94 | * This file has been auto-generated from database schema using ts-sql-codegen. 95 | * Any changes will be overwritten. 96 | */ 97 | import { Table } from "ts-sql-query/Table"; 98 | import type { DBConnection } from "../helpers/connection-source"; 99 | import { 100 | ChapterMetadataAdapter, 101 | } from "../helpers/adapters"; 102 | import { 103 | ChapterMetadata, 104 | } from "../helpers/types"; 105 | 106 | /** 107 | * Chapters information is only available for books with parseable metadata; Consumers should not assume completeness. 108 | */ 109 | export class ChaptersTable extends Table { 110 | id = this.autogeneratedPrimaryKey('id', 'int'); 111 | name = this.column('name', 'string'); 112 | bookId = this.column('book_id', 'uuid'); 113 | metadata = this.optionalColumn('metadata', 'custom', 'jsonb', ChapterMetadataAdapter); 114 | title = this.optionalColumn('title', 'string'); 115 | description = this.optionalColumn('description', 'string'); 116 | 117 | constructor() { 118 | super('chapters'); 119 | } 120 | } 121 | 122 | 123 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/generates-valid-import-on-inner-folder.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "./enums/Genre"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'enum', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/supports-custom-comparable-field-with-db-type-name.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "./enums/Genre"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'customComparable', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/supports-custom-comparable-field.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "./enums/Genre"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'customComparable', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/supports-generating-column-type-mappings.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorsTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { Table } from "ts-sql-query/Table"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | 11 | export class AuthorsTable extends Table { 12 | id = this.primaryKey('id', 'int'); 13 | name = this.optionalColumn('name', 'string'); 14 | dob = this.optionalColumn('dob', 'localDate'); 15 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 16 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 17 | 18 | constructor() { 19 | super('authors'); 20 | } 21 | } 22 | 23 | export type AuthorsCols = { 24 | id: 'int' 25 | name: 'string' 26 | dob: 'localDate' 27 | createdAt: 'localDateTime' 28 | updatedAt: 'localDateTime' 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/__snapshots__/Generator/supports-injection-of-raw-content-in-generated-files.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorsTable.ts : 2 | /* eslint-disable */ 3 | /** 4 | * DO NOT EDIT: 5 | * 6 | * This file has been auto-generated from database schema using ts-sql-codegen. 7 | * Any changes will be overwritten. 8 | */ 9 | import { Table } from "ts-sql-query/Table"; 10 | import type { DBConnection } from "../helpers/connection-source"; 11 | 12 | export class AuthorsTable extends Table { 13 | id = this.primaryKey('id', 'int'); 14 | name = this.optionalColumn('name', 'string'); 15 | dob = this.optionalColumn('dob', 'localDate'); 16 | createdAt = this.columnWithDefaultValue('created_at', 'localDateTime'); 17 | updatedAt = this.columnWithDefaultValue('updated_at', 'localDateTime'); 18 | 19 | constructor() { 20 | super('authors'); 21 | } 22 | } 23 | 24 | 25 | 26 | /* Generated on: 2020-10-10 */ -------------------------------------------------------------------------------- /test/__snapshots__/supports-custom-UUID-field-with-db-type-name.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "./enums/Genre"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'customUuid', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | -------------------------------------------------------------------------------- /test/__snapshots__/supports-custom-UUID-field.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "./enums/Genre"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'customUuid', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | -------------------------------------------------------------------------------- /test/__snapshots__/supports-custom-double-field-with-db-type-name.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "./enums/Genre"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'customDouble', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | -------------------------------------------------------------------------------- /test/__snapshots__/supports-custom-double-field.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "./enums/Genre"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'customDouble', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | -------------------------------------------------------------------------------- /test/__snapshots__/supports-custom-int-field-with-db-type-name.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "./enums/Genre"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'customInt', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | -------------------------------------------------------------------------------- /test/__snapshots__/supports-custom-int-field.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "./enums/Genre"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'customInt', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | -------------------------------------------------------------------------------- /test/__snapshots__/supports-custom-local-date-field-with-db-type-name.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "./enums/Genre"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'customLocalDate', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | -------------------------------------------------------------------------------- /test/__snapshots__/supports-custom-local-date-field.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "./enums/Genre"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'customLocalDate', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | -------------------------------------------------------------------------------- /test/__snapshots__/supports-custom-local-date-time-field-with-db-type-name.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "./enums/Genre"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'customLocalDateTime', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | -------------------------------------------------------------------------------- /test/__snapshots__/supports-custom-local-date-time-field.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "./enums/Genre"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'customLocalDateTime', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | -------------------------------------------------------------------------------- /test/__snapshots__/supports-custom-local-time-field-with-db-type-name.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "./enums/Genre"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'customLocalTime', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | -------------------------------------------------------------------------------- /test/__snapshots__/supports-custom-local-time-field.expected.txt: -------------------------------------------------------------------------------- 1 | // AuthorBooksTable.ts : 2 | /** 3 | * DO NOT EDIT: 4 | * 5 | * This file has been auto-generated from database schema using ts-sql-codegen. 6 | * Any changes will be overwritten. 7 | */ 8 | import { View } from "ts-sql-query/View"; 9 | import type { DBConnection } from "../helpers/connection-source"; 10 | import Genre from "./enums/Genre"; 11 | 12 | class AuthorBooksTable extends View { 13 | id = this.optionalColumn('id', 'uuid'); 14 | name = this.optionalColumn('name', 'string'); 15 | authorId = this.optionalColumn('author_id', 'int'); 16 | releasedAt = this.optionalColumn('released_at', 'localDate'); 17 | timeToRead = this.optionalColumn('time_to_read', 'int'); 18 | genre = this.optionalColumn('genre', 'customLocalTime', 'genre'); 19 | weightGrams = this.optionalColumn('weight_grams', 'double'); 20 | authorName = this.optionalColumn('author_name', 'string'); 21 | 22 | constructor() { 23 | super('author_books'); 24 | } 25 | } 26 | 27 | export const tAuthorBooks = new AuthorBooksTable(); 28 | 29 | -------------------------------------------------------------------------------- /test/data/test.schema.yaml: -------------------------------------------------------------------------------- 1 | name: libtest 2 | desc: "" 3 | tables: 4 | - name: public.authors 5 | type: BASE TABLE 6 | comment: "" 7 | columns: 8 | - name: id 9 | type: integer 10 | nullable: false 11 | default: null 12 | comment: "" 13 | - name: name 14 | type: varchar 15 | nullable: true 16 | default: null 17 | comment: "" 18 | - name: dob 19 | type: date 20 | nullable: true 21 | default: null 22 | comment: "" 23 | - name: created_at 24 | type: timestamp without time zone 25 | nullable: false 26 | default: now() 27 | comment: "" 28 | - name: updated_at 29 | type: timestamp without time zone 30 | nullable: false 31 | default: now() 32 | comment: "" 33 | indexes: 34 | - name: authors_pkey 35 | def: CREATE UNIQUE INDEX authors_pkey ON public.authors USING btree (id) 36 | table: public.authors 37 | columns: 38 | - id 39 | comment: "" 40 | constraints: 41 | - name: authors_pkey 42 | type: PRIMARY KEY 43 | def: PRIMARY KEY (id) 44 | table: public.authors 45 | referencedTable: "" 46 | columns: 47 | - id 48 | referencedColumns: [] 49 | comment: "" 50 | triggers: [] 51 | def: "" 52 | - name: public.books 53 | type: BASE TABLE 54 | comment: "" 55 | columns: 56 | - name: id 57 | type: uuid 58 | nullable: false 59 | default: uuid_generate_v4() 60 | comment: "" 61 | - name: name 62 | type: varchar(255) 63 | nullable: false 64 | default: null 65 | comment: "" 66 | - name: author_id 67 | type: integer 68 | nullable: false 69 | default: null 70 | comment: "" 71 | - name: released_at 72 | type: date 73 | nullable: true 74 | default: null 75 | comment: "" 76 | - name: time_to_read 77 | type: interval 78 | nullable: true 79 | default: null 80 | comment: "" 81 | - name: genre 82 | type: genre 83 | nullable: true 84 | default: null 85 | comment: "" 86 | - name: weight_grams 87 | type: double precision 88 | nullable: true 89 | default: null 90 | comment: Sample weight as provided by distributor 91 | indexes: 92 | - name: books_pkey 93 | def: CREATE UNIQUE INDEX books_pkey ON public.books USING btree (id) 94 | table: public.books 95 | columns: 96 | - id 97 | comment: "" 98 | constraints: 99 | - name: fk_books__author_id 100 | type: FOREIGN KEY 101 | def: FOREIGN KEY (author_id) REFERENCES authors(id) 102 | table: public.books 103 | referencedTable: authors 104 | columns: 105 | - author_id 106 | referencedColumns: 107 | - id 108 | comment: "" 109 | - name: books_pkey 110 | type: PRIMARY KEY 111 | def: PRIMARY KEY (id) 112 | table: public.books 113 | referencedTable: "" 114 | columns: 115 | - id 116 | referencedColumns: [] 117 | comment: "" 118 | triggers: [] 119 | def: "" 120 | - name: public.chapters 121 | type: BASE TABLE 122 | comment: Chapters information is only available for books with parseable metadata; Consumers should not assume completeness. 123 | columns: 124 | - name: id 125 | type: integer 126 | nullable: false 127 | default: nextval('chapters_id_seq'::regclass) 128 | comment: "" 129 | - name: name 130 | type: varchar 131 | nullable: false 132 | default: null 133 | comment: "" 134 | - name: book_id 135 | type: uuid 136 | nullable: false 137 | default: null 138 | comment: "" 139 | - name: metadata 140 | type: jsonb 141 | nullable: true 142 | default: null 143 | comment: "" 144 | - name: title 145 | type: varchar 146 | nullable: true 147 | default: null 148 | comment: "" 149 | - name: description 150 | type: text 151 | nullable: true 152 | default: null 153 | comment: "" 154 | indexes: 155 | - name: chapters_pkey 156 | def: CREATE UNIQUE INDEX chapters_pkey ON public.chapters USING btree (id) 157 | table: public.chapters 158 | columns: 159 | - id 160 | comment: "" 161 | constraints: 162 | - name: fk_books__book_id 163 | type: FOREIGN KEY 164 | def: FOREIGN KEY (book_id) REFERENCES books(id) 165 | table: public.chapters 166 | referencedTable: books 167 | columns: 168 | - book_id 169 | referencedColumns: 170 | - id 171 | comment: "" 172 | - name: chapters_pkey 173 | type: PRIMARY KEY 174 | def: PRIMARY KEY (id) 175 | table: public.chapters 176 | referencedTable: "" 177 | columns: 178 | - id 179 | referencedColumns: [] 180 | comment: "" 181 | triggers: [] 182 | def: "" 183 | - name: public.author_books 184 | type: VIEW 185 | comment: "" 186 | columns: 187 | - name: id 188 | type: uuid 189 | nullable: true 190 | default: null 191 | comment: "" 192 | - name: name 193 | type: varchar(255) 194 | nullable: true 195 | default: null 196 | comment: "" 197 | - name: author_id 198 | type: integer 199 | nullable: true 200 | default: null 201 | comment: "" 202 | - name: released_at 203 | type: date 204 | nullable: true 205 | default: null 206 | comment: "" 207 | - name: time_to_read 208 | type: interval 209 | nullable: true 210 | default: null 211 | comment: "" 212 | - name: genre 213 | type: genre 214 | nullable: true 215 | default: null 216 | comment: "" 217 | - name: weight_grams 218 | type: double precision 219 | nullable: true 220 | default: null 221 | comment: "" 222 | - name: author_name 223 | type: varchar 224 | nullable: true 225 | default: null 226 | comment: "" 227 | indexes: [] 228 | constraints: [] 229 | triggers: [] 230 | def: |- 231 | CREATE VIEW author_books AS ( 232 | SELECT books.id, 233 | books.name, 234 | books.author_id, 235 | books.released_at, 236 | books.time_to_read, 237 | books.genre, 238 | books.weight_grams, 239 | authors.name AS author_name 240 | FROM (authors 241 | JOIN books ON ((books.author_id = authors.id))) 242 | ) 243 | referencedTables: 244 | - public.authors 245 | - public.books 246 | relations: 247 | - table: public.books 248 | columns: 249 | - author_id 250 | parentTable: public.authors 251 | parentColumns: 252 | - id 253 | def: FOREIGN KEY (author_id) REFERENCES authors(id) 254 | virtual: false 255 | - table: public.chapters 256 | columns: 257 | - book_id 258 | parentTable: public.books 259 | parentColumns: 260 | - id 261 | def: FOREIGN KEY (book_id) REFERENCES books(id) 262 | virtual: false 263 | functions: 264 | - name: public.uuid_nil 265 | returnType: uuid 266 | arguments: "" 267 | type: FUNCTION 268 | - name: public.uuid_ns_dns 269 | returnType: uuid 270 | arguments: "" 271 | type: FUNCTION 272 | - name: public.uuid_ns_url 273 | returnType: uuid 274 | arguments: "" 275 | type: FUNCTION 276 | - name: public.uuid_ns_oid 277 | returnType: uuid 278 | arguments: "" 279 | type: FUNCTION 280 | - name: public.uuid_ns_x500 281 | returnType: uuid 282 | arguments: "" 283 | type: FUNCTION 284 | - name: public.uuid_generate_v1 285 | returnType: uuid 286 | arguments: "" 287 | type: FUNCTION 288 | - name: public.uuid_generate_v1mc 289 | returnType: uuid 290 | arguments: "" 291 | type: FUNCTION 292 | - name: public.uuid_generate_v3 293 | returnType: uuid 294 | arguments: namespace uuid, name text 295 | type: FUNCTION 296 | - name: public.uuid_generate_v4 297 | returnType: uuid 298 | arguments: "" 299 | type: FUNCTION 300 | - name: public.uuid_generate_v5 301 | returnType: uuid 302 | arguments: namespace uuid, name text 303 | type: FUNCTION 304 | driver: 305 | name: postgres 306 | databaseVersion: PostgreSQL 14.4 (Ubuntu 14.4-1.pgdg22.04+1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0, 64-bit 307 | meta: 308 | currentSchema: public 309 | searchPaths: 310 | - "\"$user\"" 311 | - public 312 | dict: {} 313 | -------------------------------------------------------------------------------- /test/data/test.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; 2 | 3 | drop view if exists author_books; 4 | drop table if exists chapters; 5 | drop table if exists books; 6 | drop type if exists genre; 7 | drop table if exists authors; 8 | 9 | create table authors ( 10 | id int not null primary key, 11 | name varchar, 12 | dob date, 13 | created_at timestamp not null default now(), 14 | updated_at timestamp not null default now() 15 | ); 16 | 17 | insert into authors (id, name) 18 | values (1, 'Will Wight'); 19 | 20 | create type genre as enum ('fantasy', 'scifi', 'horror'); 21 | 22 | create table books ( 23 | id uuid not null primary key default uuid_generate_v4(), 24 | name varchar(255) not null, 25 | author_id int not null, 26 | released_at date, 27 | time_to_read interval, 28 | genre genre, 29 | weight_grams double precision, 30 | 31 | constraint fk_books__author_id 32 | foreign key (author_id) 33 | references authors (id) 34 | ); 35 | 36 | comment on column books.weight_grams is 'Sample weight as provided by distributor'; 37 | 38 | insert into books (name, author_id) 39 | select 'Unsouled', id 40 | from authors; 41 | 42 | create table chapters ( 43 | id serial primary key, 44 | name varchar not null, 45 | book_id uuid not null, 46 | metadata jsonb, 47 | title varchar, 48 | description text, 49 | 50 | constraint fk_books__book_id 51 | foreign key (book_id) 52 | references books (id) 53 | ); 54 | 55 | comment on table chapters is 'Chapters information is only available for books with parseable metadata; Consumers should not assume completeness.'; 56 | 57 | 58 | insert into chapters (name, book_id, metadata) 59 | select 'Chapter 01', id, '{ "a": "test" }' 60 | from books; 61 | 62 | create view author_books as 63 | select 64 | books.*, 65 | authors.name as author_name 66 | from authors 67 | join books on books.author_id = authors.id; 68 | 69 | -------------------------------------------------------------------------------- /test/helpers/adapters.ts: -------------------------------------------------------------------------------- 1 | import { DefaultTypeAdapter, TypeAdapter } from "ts-sql-query/TypeAdapter" 2 | import { ChapterMetadataSchema } from "./types" 3 | 4 | export const ChapterMetadataAdapter: TypeAdapter ={ 5 | transformValueFromDB(value: any, type: string, next: DefaultTypeAdapter): unknown { 6 | console.log('Transform from db: ', value) 7 | if (type === 'jsonb') { 8 | return ChapterMetadataSchema.parse(value) 9 | } 10 | return next.transformValueFromDB(value, type) 11 | }, 12 | transformValueToDB(value: any, type: string, next: DefaultTypeAdapter): unknown { 13 | console.log('Transform to db: ', value) 14 | if (type === 'jsonb') { 15 | return value; 16 | } 17 | return next.transformValueToDB(value, type); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/helpers/connection-source.ts: -------------------------------------------------------------------------------- 1 | import { PostgreSqlConnection } from "ts-sql-query/connections/PostgreSqlConnection"; 2 | import { ConsoleLogQueryRunner } from "ts-sql-query/queryRunners/ConsoleLogQueryRunner"; 3 | import { PgPoolQueryRunner } from "ts-sql-query/queryRunners/PgPoolQueryRunner"; 4 | import { Pool } from "pg"; 5 | import memoize from "lodash/memoize"; 6 | 7 | export class DBConnection extends PostgreSqlConnection<"DBConnection"> {} 8 | 9 | export const getPool = memoize(() => { 10 | const pool = new Pool({ 11 | connectionString: process.env.DATABASE_URL, 12 | }); 13 | pool.on("error", (error: any) => { 14 | console.error("error from pg connection pool: ", error); 15 | }); 16 | return pool; 17 | }); 18 | 19 | export const getConnection = () => 20 | new DBConnection( 21 | new ConsoleLogQueryRunner(new PgPoolQueryRunner(getPool())) 22 | ); 23 | -------------------------------------------------------------------------------- /test/helpers/types.ts: -------------------------------------------------------------------------------- 1 | import * as z from "zod"; 2 | 3 | export type Genre = 'fantasy' | 'scifi' | 'horror'; 4 | 5 | export const ChapterMetadataSchema = z.object({ 6 | a: z.string(), 7 | b: z.string().optional() 8 | }) 9 | 10 | export type ChapterMetadata = z.TypeOf; 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | "rootDir": "./src", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | "outDir": "dist", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | }, 103 | "include": ["./src/**/*.ts"], 104 | "ts-node": { 105 | "transpileOnly": true 106 | } 107 | } 108 | --------------------------------------------------------------------------------