├── src ├── index.ts └── snake-naming.strategy.ts ├── jest.config.js ├── .prettierrc ├── .editorconfig ├── tsconfig.json ├── tslint.json ├── LICENSE ├── package.json ├── .gitignore ├── README.md └── tests └── snake-naming.strategy.test.ts /src/index.ts: -------------------------------------------------------------------------------- 1 | export { SnakeNamingStrategy } from './snake-naming.strategy'; 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/tests'], 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": true, 4 | "bracketSpacing": true, 5 | "trailingComma": "all", 6 | "arrowParens": "avoid" 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.json] 12 | indent_size = 2 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "allowSyntheticDefaultImports": true, 6 | "target": "es6", 7 | "noImplicitAny": true, 8 | "declaration": true, 9 | "removeComments": true, 10 | "moduleResolution": "node", 11 | "sourceMap": false, 12 | "strict": true, 13 | "outDir": "dist" 14 | }, 15 | "include": ["src/**/*"] 16 | } 17 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:recommended"], 4 | "jsRules": { 5 | "no-unused-expression": true 6 | }, 7 | "rules": { 8 | "no-return-await": true, 9 | "eofline": true, 10 | "quotemark": [true, "single"], 11 | "indent": false, 12 | "member-access": [false], 13 | "ordered-imports": [false], 14 | "max-line-length": [true, 150], 15 | "member-ordering": [false], 16 | "curly": false, 17 | "interface-name": [false], 18 | "array-type": [false], 19 | "no-empty-interface": false, 20 | "no-empty": false, 21 | "arrow-parens": false, 22 | "object-literal-sort-keys": false, 23 | "no-unused-expression": false, 24 | "max-classes-per-file": [false], 25 | "variable-name": [false], 26 | "one-line": [false], 27 | "one-variable-per-declaration": [false] 28 | }, 29 | "rulesDirectory": [] 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Toni Villena 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typeorm-naming-strategies", 3 | "version": "4.1.0", 4 | "description": "Custom naming strategies for typeorm", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "keywords": [ 8 | "typeorm", 9 | "naming", 10 | "strategy", 11 | "node", 12 | "orm", 13 | "naming strategy", 14 | "snake strategy", 15 | "typeorm snake", 16 | "naming strategies" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/tonivj5/typeorm-naming-strategies" 21 | }, 22 | "author": "Toni Villena", 23 | "license": "MIT", 24 | "scripts": { 25 | "build": "rm -rf dist && npm run tsc && cp README.md package.json dist/", 26 | "tsc": "tsc", 27 | "test": "jest" 28 | }, 29 | "prettier": { 30 | "singleQuote": true, 31 | "trailingComma": "all" 32 | }, 33 | "devDependencies": { 34 | "@types/jest": "^26.0.19", 35 | "@types/node": "^14.6.2", 36 | "prettier": "^2.1.1", 37 | "ts-jest": "^26.4.4", 38 | "tslint": "^6.1.3", 39 | "typeorm": "^0.2.18", 40 | "typescript": "^4.0.2" 41 | }, 42 | "peerDependencies": { 43 | "typeorm": "^0.2.0 || ^0.3.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dist 2 | dist 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # TypeScript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # next.js build output 64 | .next 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Typeorm naming strategies 2 | 3 | This package provides a few (one, at the moment) useful custom naming strategies. It alterates the name of columns, relations and other fields in database. 4 | 5 | For example, using the snake strategy, if you have a model like this: 6 | 7 | ```typescript 8 | class User { 9 | @Column() 10 | createdAt; 11 | } 12 | ``` 13 | 14 | In the DB the `createdAt` field will be `created_at` 15 | 16 | ## Naming strategies available 17 | 18 | - Snake 19 | 20 | ## Installation 21 | 22 | It's available as an [npm package](https://www.npmjs.com/package/typeorm-naming-strategies) 23 | 24 | ```sh 25 | npm install typeorm-naming-strategies --save 26 | ``` 27 | 28 | Or using yarn 29 | 30 | ```sh 31 | yarn add typeorm-naming-strategies 32 | ``` 33 | 34 | ## Usage 35 | 36 | ```typescript 37 | import { createConnection } from 'typeorm'; 38 | import { SnakeNamingStrategy } from 'typeorm-naming-strategies'; 39 | 40 | await createConnection({ 41 | ... 42 | namingStrategy: new SnakeNamingStrategy(), // Here you'r using the strategy! 43 | }); 44 | ``` 45 | 46 | Alternatively you can use it in combination with a `ormconfig.js` 47 | 48 | ```js 49 | // Use require instead of import 50 | const SnakeNamingStrategy = require("typeorm-naming-strategies").SnakeNamingStrategy 51 | 52 | module.exports = { 53 | ... 54 | namingStrategy: new SnakeNamingStrategy(), 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /src/snake-naming.strategy.ts: -------------------------------------------------------------------------------- 1 | // Credits to @recurrence 2 | // https://gist.github.com/recurrence/b6a4cb04a8ddf42eda4e4be520921bd2 3 | 4 | import { DefaultNamingStrategy, NamingStrategyInterface } from 'typeorm'; 5 | import { snakeCase } from 'typeorm/util/StringUtils'; 6 | 7 | export class SnakeNamingStrategy 8 | extends DefaultNamingStrategy 9 | implements NamingStrategyInterface { 10 | tableName(className: string, customName: string): string { 11 | return customName ? customName : snakeCase(className); 12 | } 13 | 14 | columnName( 15 | propertyName: string, 16 | customName: string, 17 | embeddedPrefixes: string[], 18 | ): string { 19 | return ( 20 | snakeCase(embeddedPrefixes.concat('').join('_')) + 21 | (customName ? customName : snakeCase(propertyName)) 22 | ); 23 | } 24 | 25 | relationName(propertyName: string): string { 26 | return snakeCase(propertyName); 27 | } 28 | 29 | joinColumnName(relationName: string, referencedColumnName: string): string { 30 | return snakeCase(relationName + '_' + referencedColumnName); 31 | } 32 | 33 | joinTableName( 34 | firstTableName: string, 35 | secondTableName: string, 36 | firstPropertyName: string, 37 | secondPropertyName: string, 38 | ): string { 39 | return snakeCase( 40 | firstTableName + 41 | '_' + 42 | firstPropertyName.replace(/\./gi, '_') + 43 | '_' + 44 | secondTableName, 45 | ); 46 | } 47 | 48 | joinTableColumnName( 49 | tableName: string, 50 | propertyName: string, 51 | columnName?: string, 52 | ): string { 53 | return snakeCase( 54 | tableName + '_' + (columnName ? columnName : propertyName), 55 | ); 56 | } 57 | 58 | classTableInheritanceParentColumnName( 59 | parentTableName: any, 60 | parentTableIdPropertyName: any, 61 | ): string { 62 | return snakeCase(parentTableName + '_' + parentTableIdPropertyName); 63 | } 64 | 65 | eagerJoinRelationAlias(alias: string, propertyPath: string): string { 66 | return alias + '__' + propertyPath.replace('.', '_'); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/snake-naming.strategy.test.ts: -------------------------------------------------------------------------------- 1 | import {SnakeNamingStrategy} from '../src'; 2 | 3 | describe('SnakeNamingStrategy', () => { 4 | let strategy: SnakeNamingStrategy; 5 | 6 | beforeAll(() => { 7 | strategy = new SnakeNamingStrategy(); 8 | }) 9 | 10 | describe('tableName', () => { 11 | it('should return table name in snake format when custom name was not set', () => { 12 | const testTableName = 'TestTableName'; 13 | 14 | const result = strategy.tableName(testTableName, ''); 15 | 16 | expect(result).toBe('test_table_name'); 17 | }); 18 | 19 | it('should return table name as customName when custom name was set', () => { 20 | const testTableName = 'TestTableName'; 21 | 22 | const result = strategy.tableName(testTableName, testTableName); 23 | 24 | expect(result).toBe(testTableName); 25 | }); 26 | }); 27 | 28 | describe('columnName', () => { 29 | it('should return column name in snake format when custom name was not set', () => { 30 | const testColumnName = 'testColumnName'; 31 | 32 | const result = strategy.columnName(testColumnName, '', []); 33 | 34 | expect(result).toBe('test_column_name'); 35 | }); 36 | 37 | it('should return column name as customName when custom name was set', () => { 38 | const testColumnName = 'testColumnName'; 39 | 40 | const result = strategy.columnName(testColumnName, testColumnName, []); 41 | 42 | expect(result).toBe(testColumnName); 43 | }); 44 | 45 | describe('should add prefixes to column name when embeddedPrefixes was set', () => { 46 | it('and custom name was not set', () => { 47 | const testColumnName = 'testColumnName'; 48 | const embeddedPrefixes = ['testPrefix1', 'testPrefix2']; 49 | 50 | const result = strategy.columnName(testColumnName, '', embeddedPrefixes); 51 | 52 | expect(result).toBe('test_prefix1_test_prefix2_test_column_name'); 53 | }); 54 | 55 | it('and custom name was set', () => { 56 | const testColumnName = 'testColumnName'; 57 | const embeddedPrefixes = ['testPrefix1', 'testPrefix2']; 58 | 59 | const result = strategy.columnName(testColumnName, testColumnName, embeddedPrefixes); 60 | expect(result).toBe('test_prefix1_test_prefix2_testColumnName'); 61 | }); 62 | }); 63 | }); 64 | 65 | describe('relationName', () => { 66 | it('should return property name as snake case', () => { 67 | const result = strategy.relationName('testPropertyName'); 68 | expect(result).toBe('test_property_name'); 69 | }); 70 | }); 71 | 72 | describe('joinColumnName', () => { 73 | it('should return relation name and referenced column name joined by "_" as snake case', () => { 74 | const result = strategy.joinColumnName( 75 | 'testRelationName', 'testReferencedColumnName'); 76 | expect(result).toBe('test_relation_name_test_referenced_column_name'); 77 | }); 78 | }); 79 | 80 | describe('joinTableName', () => { 81 | it('should return table names and first property name as snake case', () => { 82 | const result = strategy.joinTableName( 83 | 'firstTable', 84 | 'secondTable', 85 | 'first.propertyName', 86 | 'secondProperty' 87 | ); 88 | expect(result).toBe('first_table_first_property_name_second_table'); 89 | }); 90 | }); 91 | 92 | describe('joinTableColumnName', () => { 93 | it('should return table name and column name as snake case when columnName was set', () => { 94 | const result = strategy.joinTableColumnName( 95 | 'tableName', 96 | 'propertyName', 97 | 'columnName' 98 | ); 99 | expect(result).toBe('table_name_column_name'); 100 | }); 101 | 102 | it('should return table name and property name as snake case when columnName was not set', () => { 103 | const result = strategy.joinTableColumnName( 104 | 'tableName', 105 | 'propertyName', 106 | undefined 107 | ); 108 | expect(result).toBe('table_name_property_name'); 109 | }); 110 | }); 111 | 112 | describe('eagerJoinRelationAlias', () => { 113 | it('should join alias and property path by "__"', () => { 114 | const result = strategy.eagerJoinRelationAlias('testAlias', 'test.propertyPath'); 115 | expect(result).toBe('testAlias__test_propertyPath'); 116 | }); 117 | }); 118 | 119 | describe('classTableInheritanceParentColumnName', () => { 120 | it('should join parent table name and property name by "_" and return as snake case', () => { 121 | const result = strategy.classTableInheritanceParentColumnName( 122 | 'parentTableName', 123 | 'testPropertyName' 124 | ); 125 | expect(result).toBe('parent_table_name_test_property_name'); 126 | }); 127 | }); 128 | }); 129 | --------------------------------------------------------------------------------