├── .github └── FUNDING.yml ├── .gitignore ├── .npmignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── jest.config.js ├── package.json ├── src ├── caseConventions.ts ├── cli.ts ├── convention-store.ts └── convention-transformer.ts ├── test ├── __fixtures__ │ ├── cascading-deletion-and-fk.schema.prisma │ ├── columns-with-view-in-name.schema.prisma │ ├── comments-on-model-lines.schema.prisma │ ├── complex-index.schema.prisma │ ├── disable.prisma-case-format │ ├── disable.schema.prisma │ ├── disable2.prisma-case-format │ ├── disable2.schema.prisma │ ├── enum-tables-map.schema.prisma │ ├── enum.schema.prisma │ ├── idempotency.schema.prisma │ ├── issue.schema.prisma │ ├── issue2.schema.prisma │ ├── model-columns-with-underscores.schema.prisma │ ├── next-auth.schema.prisma │ ├── pluralize-fields.schema.prisma │ ├── readme-demo.schema.prisma │ ├── tables-with-pluralized-db-targets.schema.prisma │ └── views.schema.prisma ├── __snapshots__ │ └── convention-transformer.test.ts.snap └── convention-transformer.test.ts ├── tsconfig.json └── yarn.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | polar: iiian 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | bin 4 | .vscode 5 | coverage 6 | pnpm-lock.* 7 | package-lock.* 8 | schema.prisma 9 | .prisma-schema-format -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | coverage/ 3 | .vscode/ 4 | bin/**.js.map 5 | pnpm-lock.yaml 6 | schema.prisma 7 | .schema.prisma 8 | src/ 9 | tsconfig.json 10 | jest.config.js -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "DoDryRun", 9 | "type": "node", 10 | "request": "launch", 11 | "args": [ 12 | "${workspaceFolder}/src/cli.ts", 13 | "--map-table-case=snake", 14 | "--map-field-case=snake", 15 | "--pluralize", 16 | "--dry-run" 17 | ], 18 | "runtimeArgs": [ 19 | "--nolazy", 20 | "-r", 21 | "ts-node/register" 22 | ], 23 | "sourceMaps": true, 24 | "cwd": "${workspaceRoot}", 25 | "protocol": "inspector", 26 | }, 27 | { 28 | "name": "Debug Jest Tests", 29 | "type": "node", 30 | "request": "launch", 31 | "runtimeArgs": [ 32 | "--inspect-brk", 33 | "${workspaceRoot}/node_modules/jest/bin/jest.js", 34 | "--runInBand" 35 | ], 36 | "console": "integratedTerminal", 37 | "internalConsoleOptions": "neverOpen" 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Ian Ray 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ⚠️ℹ️ LOOKING FOR NEW MAINTAINER! ⚠️ℹ️ 2 | 3 | To be blunt: *I don't care about this project anymore. At all. I have no interest or desire to maintain it.* But, I totally get it if you're somebody still getting something out of it. If you'd like to take over this project, drop a line. It's yours! 4 | 5 | # prisma-case-format 6 | 7 | `prisma introspect` names its model 1:1 with your database's conventions. Most of the time that is probably fine, however you may want or need different conventions in the generated client library. `prisma-case-format` makes it simple and direct to get the case conventions you want, applied across an entire `schema.prisma` with optional overrides per `model` or `field`. 8 | 9 | ## Use Cases 10 | ### As a one-time migration assistant 11 | 12 | Did `prisma introspect` on your huge database schema mis-case all your tables & fields? Is it wrecking your hope of using duck-types? Use `--dry-run` in combination with `--(map)?-(table|field|enum)-case` to figure out which case conventions are correct for your project. Once things look correct, drop the flag to save changes to the specified `--file`, which is your local root `schema.prisma` by default. 13 | 14 | ### As a dedicated linter for your `schema.prisma` 15 | 16 | `prisma-case-format` aims to be idempotent, so you can use it to confirm that case conventions in your `schema.prisma` have not accidentally drifted. `prisma-case-format` can be applied on-commit or on-push, either in a git commit-hook package or as a CI/CD step. Use `--dry-run` to diff changes with the original file, or backup the original and compare to the edit. 17 | 18 | ### With `NextAuth.js` 19 | 20 | If your team is using `NextAuth.js`, you may have encountered an issue where `prisma-case-format` steam rolls the strict data contract expected by the `NextAuth.js` integration. Specify the `--uses-next-auth` flag in order to protect your `NextAuth.js` tables from your specified conventions. 21 | 22 | ### With varying conventions 23 | 24 | If you inherited or were forced to produce a database schema that has deviations in case conventions, `prisma-case-format` can ensure these conventions remain stable. See the [config file](#config-file) section below. 25 | 26 | ## Usage 27 | 28 | ```bash 29 | ❯ prisma-case-format --help 30 | Usage: prisma-case-format [options] 31 | 32 | Give your schema.prisma sane naming conventions 33 | 34 | Options: 35 | -f, --file cwd-relative path to `schema.prisma` file (default: "schema.prisma") 36 | -c, --config-file cwd-relative path to `.prisma-case-format` config file (default: ".prisma-case-format") 37 | -D, --dry-run print changes to console, rather than back to file (default: false) 38 | --table-case case convention for table names (SEE BOTTOM) (default: "pascal") 39 | --field-case case convention for field names (default: "camel") 40 | --enum-case case convention for enum names. In case of not declared, uses value of “--table-case”. (default: "pascal") 41 | --map-table-case case convention for @@map() annotations (SEE BOTTOM) 42 | --map-field-case case convention for @map() annotations 43 | --map-enum-case case convention for @map() annotations of enums. In case of not declared, uses value of “--map-table-case”. 44 | -p, --pluralize optionally pluralize array type fields (default: false) 45 | --uses-next-auth guarantee next-auth models (Account, User, Session, etc) uphold their data-contracts 46 | -V, --version hint: you have v2.1.0 47 | -h, --help display help for command 48 | ------------------------- 49 | Supported case conventions: ["pascal", "camel", "snake"]. 50 | Additionally, append ',plural' after any case-convention selection to mark case convention as pluralized. 51 | > For instance: 52 | --map-table-case=snake,plural 53 | 54 | will append `@@map("users")` to `model User`. 55 | Append ',singular' after any case-convention selection to mark case convention as singularized. 56 | > For instance, 57 | --map-table-case=snake,singular 58 | 59 | will append `@@map("user")` to `model Users` 60 | 61 | Deviant case conventions altogether: 62 | > If one or more of your models or fields needs to opt out of case conventions, either to be a fixed name or to disable it, 63 | use the `.prisma-case-format` file and read the documentation on "deviant name mappings". 64 | ``` 65 | 66 | ## Example 67 | 68 | ```prisma 69 | // schema.prisma before 70 | ... 71 | model house_rating { 72 | id Int @id @default(autoincrement()) 73 | house_id String 74 | house house @relation(fields: [house_id], references: [id]) 75 | ... 76 | } 77 | ... 78 | model house { 79 | id String @id @default(uuid()) 80 | house_rating house_rating[] 81 | ... 82 | } 83 | ... 84 | ``` 85 | 86 | ```bash 87 | ❯ prisma-case-format 88 | ✨ Done. 89 | ``` 90 | 91 | ```prisma 92 | // schema.prisma after 93 | ... 94 | model HouseRating { 95 | id Int @id @default(autoincrement()) 96 | houseId String @map("house_id") 97 | house House @relation(fields: [houseId], references: [id]) 98 | ... 99 | 100 | @@map("house_rating") 101 | } 102 | ... 103 | model House { 104 | id String @id @default(uuid()) 105 | houseRating HouseRating[] 106 | ... 107 | 108 | @@map("house") 109 | } 110 | ... 111 | ``` 112 | 113 | ## Drift Protection 114 | 115 | `prisma-case-format` lets you manage three case conventions: `table`, `field`, and `enum`. 116 | 117 | ### `table` 118 | 119 | ```prisma 120 | model Example { // <- controlled by `--table-case` 121 | id String @id 122 | value1 String @map("value_1") 123 | @@map("example") // <- controlled by `--map-table-case` 124 | } 125 | ``` 126 | 127 | Table conventions are controlled by the `--table-case` & `--map-table-case` flags. `--table-case` specifies the case convention for the models in the generated prisma client library. `--map-table-case` will manage the database name case convention. **`table` args manage models & views**. 128 | 129 | ### `field` 130 | 131 | ```prisma 132 | model Example { 133 | id String @id 134 | // r-- controlled by `--field-case` 135 | // | r-- controlled by `--map-field-case` 136 | // v v 137 | value1 String @map("value_1") 138 | } 139 | ``` 140 | 141 | Field conventions are controlled by the `--field-case` & `--map-field-case` flags. `--field-case` specifies the case convention for the fields in models within the generated prisma client library. `--map-field-case` will manage the case convention for the field in the database. **`field` args do not apply to enums**. 142 | 143 | ### `enum` 144 | 145 | ```prisma 146 | enum Example { // <- controlled by --enum-case 147 | Value1 148 | Value2 149 | @@map("example") // <- controlled by --map-enum-case 150 | } 151 | ``` 152 | 153 | Enum conventions are controlled by the `--enum-case` & `--map-enum-case` flags. `--enum-case` specifies the case convention for the enums in the generated prisma client library. `--map-enum-case` will manage the case convention for the enum within the database. **`enum` args do not apply to enum values, just the model & database names**. 154 | 155 | ## Config file 156 | `prisma-case-format` supports a config file, which primarily exists as a means to *override* case conventions per model or per field. This can be especially useful if you are coming from an existing database that doesn't have perfect naming convention consistency. For example, some of your model fields are `snake_case`, others are `camelCase`. 157 | 158 | By default, this file is located at `.prisma-case-format`. The file is expected to be `yaml` internally. The following section details the config file format. 159 | 160 | ### Config file format 161 | 162 | #### Property: `default?: string`: 163 | 164 | An alternative to specifying the commandline arguments. The format is a simple `;`-delimited list, white space allowed. 165 | 166 | ##### Example: 167 | ```yaml 168 | default: 'table=pascal; mapTable=snake; field=pascal; mapField=snake; enum=pascal; mapEnum=pascal' 169 | uses_next_auth: false 170 | ``` 171 | 172 | #### Property: `override?: Dictionary` 173 | 174 | #### Type: `Field=Dictionary` 175 | 176 | Controls overrides on a per-model & per-field basis. Works for `models`, `views` and `enums`, in the same scope as the `table`/`field`/`enum` argument groups when running in commandline mode. Each key in `override` & subkey in `field` is allowed to be a regex, in which case it will attempt to match based on the specified pattern. This can be useful if you have several fields with prefixes or suffixes. 177 | 178 | ##### Example 179 | ```yaml 180 | default: 'table=pascal; mapTable=snake; field=pascal; mapField=snake; enum=pascal; mapEnum=pascal' 181 | override: 182 | MyTable: 183 | # don't fret about case convention here; 184 | # prisma-case-format considers "MyTable" to be equivalent to "my_table" & "myTable" 185 | default: 'table=snake;mapTable=camel;field=snake;mapField=snake;enum=snake;mapEnum=snake' 186 | field: 187 | # same here. this is equivalent to "foo_field_on_my_table" 188 | fooFieldOnMyTable: 'field=pascal;mapField=pascal' 189 | 'my_prefix.*': 'mapField=pascal;' # inherits field=snake from `MyTable.default` 190 | ``` 191 | 192 | Results in: 193 | 194 | ```prisma 195 | model my_table { 196 | id String @id 197 | FooFieldOnMyTable Integer 198 | my_prefix_prop_a Json @map("MyPrefixPropA") 199 | 200 | @@map("myTable") 201 | } 202 | ``` 203 | 204 | #### Disabling case convention management 205 | 206 | The `.prisma-case-format` file supports specifying that a particular model or field has its case convention management "disabled". This is achieved by setting the key under `override` or subkey under `field` to `'disable'`. 207 | 208 | ##### Example: 209 | 210 | ```yaml 211 | default: ... 212 | override: 213 | mYTaBlE: 'disable' # skip convention management for this table 214 | ... 215 | YourTable: 216 | default: '...' 217 | field: 218 | unmanaged_property: 'disable' # skip convention management for this field 219 | ``` 220 | 221 | Results in: 222 | 223 | ```prisma 224 | model mYTaBlE { 225 | nowican String @id 226 | cAnFAlL_iNtO_UtTeR__DiSrePaiR String @map("lol") 227 | iN_T0T4L_p34C3 Integer @map("great") 228 | 229 | @map("myspace") 230 | } 231 | ``` 232 | 233 | The other tables surrounding `mYTaBlE` will remain managed. 234 | 235 | #### Deviant name mappings 236 | 237 | In some cases, some users will want to let certain model or field names deviate out of using case-conventions. Primarily, this is useful if you're 238 | inheriting ill-named tables in an at least partialy database-first workflow. 239 | Overriding case-convention use on a specific target name can be achieved in a `.prisma-case-format` file by using the syntax `map(Table|Field|Enum)=!`, 240 | where `` is your precise **case-sensitive** name that you want to map. 241 | 242 | ##### Example 243 | 244 | ```yaml 245 | default: ... 246 | override: 247 | MyAmazingModel: 248 | default: 'mapTable=!Amaze_o' 249 | field: 250 | fuzz: 'mapField=!fizz_buzz' 251 | MySuperDuperEnum: 252 | default: 'mapTable=!myenum' 253 | ``` 254 | 255 | Results in: 256 | 257 | ```prisma 258 | model MyAmazingModel { 259 | id Int @id 260 | fuzz String @map("fizz_buzz") 261 | 262 | @@map("Amaze_o") 263 | } 264 | 265 | enum MySuperDuperEnum { 266 | Opt1 267 | Opt2 268 | 269 | @@map("myenum") 270 | } 271 | ``` 272 | 273 | #### Property: `uses_next_auth?: boolean` 274 | 275 | If `=true`, then [the models added by `NextAuth.js`](https://authjs.dev/reference/adapter/prisma) will not have their case conventions rewritten by user's selected case conventions, preserving the expected contract. **If you are not using `NextAuth.js`, it is suggested to keep this unspecified or `false`.** 276 | This property is equivalent to the following configuration: 277 | 278 | ```yaml 279 | uses_next_auth: false 280 | default: ... 281 | override: 282 | Account: 283 | default: 'table=pascal; mapTable=pascal;' 284 | field: 285 | id: 'field=camel; mapField=camel' 286 | userId: 'field=camel; mapField=camel' 287 | type: 'field=camel; mapField=camel' 288 | provider: 'field=camel; mapField=camel' 289 | providerAccountId: 'field=camel; mapField=camel' 290 | refresh_token: 'field=snake; mapField=snake' 291 | access_token: 'field=snake; mapField=snake' 292 | expires_at: 'field=snake; mapField=snake' 293 | token_type: 'field=snake; mapField=snake' 294 | scope: 'field=snake; mapField=snake' 295 | id_token: 'field=snake; mapField=snake' 296 | session_state: 'field=snake; mapField=snake' 297 | user: 'field=snake; mapField=snake' 298 | Session: 299 | default: 'table=pascal; mapTable=pascal; field=camel; mapField=camel' 300 | User: 301 | default: 'table=pascal; mapTable=pascal; field=camel; mapField=camel' 302 | VerificationToken: 303 | default: 'table=pascal; mapTable=pascal; field=camel; mapField=camel' 304 | ``` 305 | 306 | Note that if `uses_next_auth: true` and your `overrides` section contains conflicting specifications for any of the above model names (`Account`, `Session`, `User`, or `VerificationToken`), `uses_next_auth` will blow away your configuration rules for those models and use the above rules. 307 | 308 | 309 | ## Pluralization 310 | 311 | Supply the `-p` or `--pluralize` argument to get array pluralizations. 312 | 313 | ```bash 314 | ❯ prisma-case-format -p 315 | ✨ Done. 316 | ``` 317 | 318 | ```prisma 319 | // schema.prisma after pluralization 320 | ... 321 | model House { 322 | ... 323 | houseRatings HouseRating[] 324 | ownerContacts String[] @map("owner_contact") 325 | ... 326 | } 327 | ... 328 | ``` 329 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | transform: { 6 | "^.+\\.tsx?$": "ts-jest", 7 | }, 8 | testRegex: "(/test/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 9 | testPathIgnorePatterns: ["/dist/", "/src/", "/node_modules/"], 10 | moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], 11 | collectCoverage: true, 12 | mapCoverage: true, 13 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prisma-case-format", 3 | "version": "2.2.1", 4 | "description": "Give your introspected schema.prisma sane casing conventions", 5 | "main": "./dist/cli.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/iiian/prisma-case-format.git" 9 | }, 10 | "bin": { 11 | "prisma-case-format": "bin/cli.js" 12 | }, 13 | "scripts": { 14 | "build": "tsc", 15 | "test": "./node_modules/jest/bin/jest.js" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "devDependencies": { 20 | "@babel/core": "^7.17.2", 21 | "@babel/preset-env": "^7.16.11", 22 | "@babel/preset-typescript": "^7.16.7", 23 | "@types/jest": "^27.4.0", 24 | "@types/js-yaml": "^4.0.9", 25 | "@types/node": "^17.0.17", 26 | "@types/pluralize": "0.0.29", 27 | "babel-jest": "^27.5.1", 28 | "jest": "^29.0.0", 29 | "ts-jest": "^29.0.0", 30 | "ts-node": "^10.5.0", 31 | "ts-toolbelt": "9.6.0", 32 | "typescript": "^5.0.0" 33 | }, 34 | "dependencies": { 35 | "@prisma/internals": "^4.12.0", 36 | "chalk": "^4.0.0", 37 | "change-case": "4.1.2", 38 | "commander": "7.2.0", 39 | "js-yaml": "4.1.0", 40 | "pluralize": "8.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/caseConventions.ts: -------------------------------------------------------------------------------- 1 | import { Options } from 'change-case'; 2 | import pluralize, { singular } from 'pluralize'; 3 | 4 | export type CaseChange = (input: string, options?: Options) => string; 5 | 6 | /** Modify a case change function to output pluralized result */ 7 | export function asPluralized(f: CaseChange): CaseChange { 8 | return (input: string, opt?: Options) => pluralize(f(input, opt)); 9 | } 10 | 11 | export function asSingularized(f: CaseChange): CaseChange { 12 | return (input: string, opt?: Options) => singular(f(input, opt)); 13 | } -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | import { readFileSync, writeFileSync } from 'fs'; 4 | import { Command, OptionValues } from 'commander'; 5 | import chalk from 'chalk'; 6 | import { formatSchema } from '@prisma/internals'; 7 | import { ConventionTransformer } from './convention-transformer'; 8 | import { ConventionStore, DEFAULT_PRISMA_CASE_FORMAT_FILE_LOCATION, SUPPORTED_CASE_CONVENTIONS_MESSAGE, tryGetTableCaseConvention } from './convention-store'; 9 | import { resolve } from 'path'; 10 | 11 | const DEFAULT_FILE_LOCATION = 'schema.prisma'; 12 | const program = new Command(`prisma-case-format`); 13 | 14 | const VERSION = require('../package.json').version; 15 | 16 | program 17 | .description(`Give your schema.prisma sane naming conventions`) 18 | .addHelpText('after', SUPPORTED_CASE_CONVENTIONS_MESSAGE) 19 | .requiredOption('-f, --file ', 'cwd-relative path to `schema.prisma` file', DEFAULT_FILE_LOCATION) 20 | .option('-c, --config-file ', 'cwd-relative path to `.prisma-case-format` config file', DEFAULT_PRISMA_CASE_FORMAT_FILE_LOCATION) 21 | .option('-D, --dry-run', 'print changes to console, rather than back to file', false) 22 | .option('--table-case ', 'case convention for table names (SEE BOTTOM)', 'pascal') 23 | .option('--field-case ', 'case convention for field names', 'camel') 24 | .option('--enum-case ', 'case convention for enum names. In case of not declared, uses value of “--table-case”.', 'pascal') 25 | .option('--map-table-case ', 'case convention for @@map() annotations (SEE BOTTOM)') 26 | .option('--map-field-case ', 'case convention for @map() annotations') 27 | .option('--map-enum-case ', 'case convention for @map() annotations of enums. In case of not declared, uses value of “--map-table-case”.') 28 | .option('-p, --pluralize', 'optionally pluralize array type fields', false) 29 | .option('--uses-next-auth', 'guarantee next-auth models (Account, User, Session, etc) uphold their data-contracts') 30 | .version(VERSION, '', `hint: you have v${VERSION}`) 31 | ; 32 | program.parse(process.argv); 33 | 34 | run(); 35 | 36 | async function run() { 37 | const options = program.opts(); 38 | 39 | if (options.dryRun) { 40 | console.log('***Dry run mode***'); 41 | } 42 | 43 | const [file_contents, err] = tryGetFileContents(options); 44 | if (err) { 45 | console.error(chalk.red("Encountered an error while trying to read provided schema.prisma file at path " + options.file)); 46 | console.error(chalk.red(err.message)); 47 | process.exit(1); 48 | } 49 | 50 | const [conv, conv_err] = ConventionStore.fromFile(resolve(options.configFile)); 51 | if (conv_err) { 52 | console.error(chalk.red("Encountered an error while trying to read provided config file at path " + options.convFile)); 53 | console.error(chalk.red(conv_err.message)); 54 | process.exit(1); 55 | } 56 | 57 | if (options.tableCase) { 58 | let [tableCaseConvention, err] = tryGetTableCaseConvention(options.tableCase); 59 | if (err) { 60 | console.warn(chalk.yellow(`Warning: encountered unsupported case convention: "${options.fieldCase}". Defaulting to "pascal" case.`)); 61 | [tableCaseConvention,] = tryGetTableCaseConvention('pascal'); 62 | } 63 | 64 | conv!.tableCaseConvention = tableCaseConvention!; 65 | } 66 | 67 | if (options.fieldCase) { 68 | let [fieldCaseConvention, err] = tryGetTableCaseConvention(options.fieldCase); 69 | if (err) { 70 | console.warn(chalk.yellow(`Warning: encountered unsupported case convention: "${options.fieldCase}". Defaulting to "camel" case.`)); 71 | [fieldCaseConvention,] = tryGetTableCaseConvention('camel'); 72 | } 73 | 74 | conv!.fieldCaseConvention = fieldCaseConvention!; 75 | } 76 | 77 | if (options.enumCase) { 78 | let [caseConvention, err] = tryGetTableCaseConvention(options.enumCase); 79 | if (err) { 80 | console.warn(chalk.yellow(`Warning: encountered unsupported case convention: "${options.enumCase}". Defaulting to "pascal" case.`)); 81 | [caseConvention,] = tryGetTableCaseConvention('pascal'); 82 | } 83 | 84 | conv!.enumCaseConvention = caseConvention!; 85 | } 86 | 87 | if (options.mapTableCase) { 88 | const opt_case: string = options.mapTableCase; 89 | let [convention, err] = tryGetTableCaseConvention(opt_case); 90 | if (err) { 91 | console.error(chalk.red(`Error: encountered unsupported case convention for --map-table-case: "${opt_case}".`)); 92 | console.error(chalk.red(`Suggestion: ${SUPPORTED_CASE_CONVENTIONS_MESSAGE}`)); 93 | program.outputHelp(); 94 | process.exit(1); 95 | } else { 96 | conv!.mapTableCaseConvention = convention!; 97 | } 98 | } 99 | 100 | if (options.mapFieldCase) { 101 | const opt_case: string = options.mapFieldCase; 102 | let [convention, err] = tryGetTableCaseConvention(opt_case); 103 | if (err) { 104 | console.error(chalk.red(`Error: encountered unsupported case convention for --map-field-case: "${opt_case}".`)); 105 | console.error(chalk.red(`Suggestion: ${SUPPORTED_CASE_CONVENTIONS_MESSAGE}`)); 106 | program.outputHelp(); 107 | process.exit(1); 108 | } else { 109 | conv!.mapFieldCaseConvention = convention!; 110 | } 111 | } 112 | 113 | if (options.mapEnumCase) { 114 | const opt_case: string = options.mapEnumCase; 115 | let [convention, err] = tryGetTableCaseConvention(opt_case); 116 | if (err) { 117 | console.error(chalk.red(`Error: encountered unsupported case convention for --map-enum-case: "${opt_case}".`)); 118 | console.error(chalk.red(`Suggestion: ${SUPPORTED_CASE_CONVENTIONS_MESSAGE}`)); 119 | program.outputHelp(); 120 | process.exit(1); 121 | } else { 122 | conv!.mapEnumCaseConvention = convention!; 123 | } 124 | } 125 | 126 | conv!.pluralize = !!options.pluralize; 127 | 128 | const [schema, schema_error] = ConventionTransformer.migrateCaseConventions(file_contents!, conv!); 129 | if (schema_error) { 130 | console.error(chalk.red('Encountered error while migrating case conventions')); 131 | console.error(chalk.red(schema_error)); 132 | process.exit(1); 133 | } 134 | 135 | const new_schema = await formatSchema({ schema: schema! }); 136 | 137 | if (options.dryRun) { 138 | console.log(new_schema); 139 | process.exit(0); 140 | } 141 | writeFileSync(options.file, Buffer.from(new_schema), { encoding: 'utf8' }); 142 | console.log(chalk.blue('✨ Done.')); 143 | } 144 | 145 | export function tryGetFileContents(options: OptionValues): [string?, Error?] { 146 | const file_path = options.file; 147 | try { 148 | const contents = String(readFileSync(file_path)); 149 | return [contents,]; 150 | } catch (error) { 151 | return [, error as Error]; 152 | } 153 | } -------------------------------------------------------------------------------- /src/convention-store.ts: -------------------------------------------------------------------------------- 1 | import { camelCase, pascalCase, snakeCase } from 'change-case'; 2 | import { CaseChange, asPluralized, asSingularized } from './caseConventions'; 3 | import { existsSync, readFileSync } from 'fs'; 4 | import * as jsyaml from 'js-yaml'; 5 | import { resolve } from 'path'; 6 | 7 | export function tryGetTableCaseConvention(raw_type: string): [CaseChange?, Error?] { 8 | const [type, case_flavor] = raw_type.split(','); 9 | let kase: CaseChange; 10 | switch (type) { 11 | case 'pascal': 12 | kase = pascalCase; 13 | break; 14 | case 'camel': 15 | kase = camelCase; 16 | break; 17 | case 'snake': 18 | kase = snakeCase; 19 | break; 20 | case 'false': 21 | case 'true': 22 | case 'disable': 23 | kase = x => x; 24 | break; 25 | default: return [, new Error('unsupported case convention: ' + type)]; 26 | } 27 | 28 | switch (case_flavor) { 29 | case 'plural': 30 | kase = asPluralized(kase); 31 | break; 32 | case 'singular': 33 | kase = asSingularized(kase); 34 | break; 35 | } 36 | 37 | return [kase,]; 38 | } 39 | 40 | export const SUPPORTED_CASE_CONVENTIONS_MESSAGE = `------------------------- 41 | Supported case conventions: ["pascal", "camel", "snake"]. 42 | Additionally, append ',plural' after any case-convention selection to mark case convention as pluralized. 43 | For instance: 44 | --map-table-case=snake,plural 45 | 46 | will append \`@@map("users")\` to \`model User\`. 47 | Append ',singular' after any case-convention selection to mark case convention as singularized. 48 | For instance, 49 | --map-table-case=snake,singular 50 | 51 | will append \`@@map("user")\` to \`model Users\``; 52 | 53 | export type ObjectConvention = { 54 | table?: string; 55 | field?: string; 56 | enum?: string; 57 | mapTable?: string; 58 | mapField?: string; 59 | mapEnum?: string; 60 | }; 61 | 62 | export type Convention = 63 | | Record 64 | | string; 65 | 66 | export type ObjectModelConvention = { 67 | default?: Convention; 68 | field?: Convention; 69 | }; 70 | 71 | export type ModelConvention = 72 | | ObjectModelConvention 73 | | string; 74 | 75 | export type ConventionFile = { 76 | default?: string; 77 | uses_next_auth?: boolean; 78 | override?: Record; 79 | }; 80 | 81 | export const DEFAULT_PRISMA_CASE_FORMAT_FILE_LOCATION = '.prisma-case-format'; 82 | 83 | export class ConventionStore { 84 | public static fromFile(path: string, usesNextAuth?: boolean): [ConventionStore?, Error?] { 85 | if (!existsSync(path)) { 86 | if (path === resolve(DEFAULT_PRISMA_CASE_FORMAT_FILE_LOCATION)) { 87 | return ConventionStore.fromConf({}); 88 | } 89 | return [, new Error('file does not exist: ' + path)] 90 | } 91 | return ConventionStore.fromConfStr(readFileSync(path).toString()); 92 | } 93 | 94 | public static fromConfStr(conf: string): [ConventionStore?, Error?] { 95 | let content = jsyaml.load(conf); 96 | return ConventionStore.fromConf(content); 97 | } 98 | 99 | public static fromConf(conf: ConventionFile): [ConventionStore?, Error?] { 100 | if (conf.uses_next_auth) { 101 | conf = imbueWithNextAuth(conf); 102 | } 103 | const children: Record = {}; 104 | for (const entity_name in conf.override) { 105 | const entity = conf.override[entity_name]; 106 | let conv: ConventionStore; 107 | if (!entity) { 108 | return [, Error(`${entity_name} was ${entity}, but should be of type string`)]; 109 | } 110 | switch (typeof entity) { 111 | case 'string': { 112 | let [c, err] = ConventionStore.fromString(entity); 113 | if (err) { 114 | return [, err]; 115 | } 116 | conv = c!; 117 | break; 118 | } 119 | case 'object': { 120 | let [c, err] = ConventionStore.fromObjectModel(entity); 121 | if (err) { 122 | return [, err]; 123 | } 124 | conv = c!; 125 | break; 126 | } 127 | } 128 | children[entity_name] = conv; 129 | } 130 | let [conv, err] = conf.default ? ConventionStore.fromString(conf.default) : [new ConventionStore(),]; 131 | if (err) { 132 | return [, err]; 133 | } 134 | conv!.children = children; 135 | return [conv]; 136 | } 137 | 138 | static fromObjectModel(entity: ObjectModelConvention): [ConventionStore?, Error?] { 139 | const conv = new ConventionStore(); 140 | const children: Record = {}; 141 | if (entity.default) { 142 | const err = conv._applyConventions(entity.default as string); 143 | if (err) { 144 | return [, err]; 145 | } 146 | } 147 | switch (typeof entity.field) { 148 | case 'string': { 149 | const [conv, err] = ConventionStore.fromString(entity.field); 150 | if (err) { 151 | return [, err]; 152 | } 153 | children['.*'] = conv!; 154 | break; 155 | } 156 | case 'object': { 157 | for (const field in entity.field) { 158 | const [conv, err] = ConventionStore.fromString(entity.field[field]); 159 | if (err) { 160 | return [, err]; 161 | } 162 | children[field] = conv!; 163 | } 164 | break; 165 | } 166 | case 'undefined': break; 167 | default: return [,new Error(`unexpected type ${typeof entity.field} for value ${JSON.stringify(entity.field)}. Expected object with properties: (default, field).`)]; 168 | } 169 | conv.children = children; 170 | return [conv,]; 171 | } 172 | 173 | static fromString(entity: string): [ConventionStore?, Error?] { 174 | if (entity === 'disable') { 175 | const conv = new ConventionStore(); 176 | conv.disable = true; 177 | return [conv,]; 178 | } 179 | const conv = new ConventionStore(); 180 | const err = conv._applyConventions(entity); 181 | if (err) { 182 | return [,err]; 183 | } 184 | return [conv,]; 185 | } 186 | 187 | private _applyConventions(entity: string): Error|undefined { 188 | const conventions = entity.split(';').filter(Boolean).map(choice => choice.split('=').map(e => e.trim()) as [string, string]); 189 | for (const [option, choice] of conventions) { 190 | if (option.startsWith('map') && choice.startsWith('!')) { 191 | const key: 'mapTableCaseConvention' | 'mapFieldCaseConvention' | 'mapEnumCaseConvention' = (option + 'CaseConvention') as any; 192 | const static_choice = choice.slice(1); 193 | this[key] = () => static_choice; 194 | continue; 195 | } 196 | const [sel, err] = tryGetTableCaseConvention(choice); 197 | if (err) { 198 | return err; 199 | } 200 | switch(option) { 201 | case 'table': this.tableCaseConvention = sel; break; 202 | case 'enum': this.enumCaseConvention = sel; break; 203 | case 'field': this.fieldCaseConvention = sel; break; 204 | case 'mapTable': this.mapTableCaseConvention = sel; break; 205 | case 'mapEnum': this.mapEnumCaseConvention = sel; break; 206 | case 'mapField': this.mapFieldCaseConvention = sel; break; 207 | case 'pluralize': this.pluralize = (['disable', 'false'].includes(choice)) ? false : true; break; 208 | default: return new Error(`unrecognized mapping option specified: ${option}, valid options are: ["table", "enum", "field", "mapTable", "mapEnum", "mapField", "pluralize"]`); 209 | } 210 | if (choice.includes('singular')) { 211 | this.pluralize = false; 212 | } 213 | if (choice.includes('plural')) { 214 | this.pluralize = true; 215 | } 216 | } 217 | return undefined; 218 | } 219 | 220 | static fromConventions(case_conv: CaseConventions) { 221 | const conv = new ConventionStore(); 222 | conv.tableCaseConvention = case_conv.tableCaseConvention; 223 | conv.mapTableCaseConvention = case_conv.mapTableCaseConvention; 224 | conv.enumCaseConvention = case_conv.enumCaseConvention; 225 | conv.mapEnumCaseConvention = case_conv.mapEnumCaseConvention; 226 | conv.fieldCaseConvention = case_conv.fieldCaseConvention; 227 | conv.mapFieldCaseConvention = case_conv.mapFieldCaseConvention; 228 | conv.pluralize = case_conv.pluralize; 229 | conv.disable = case_conv.disable; 230 | 231 | return conv; 232 | } 233 | 234 | public disable?: boolean = false; 235 | protected constructor( 236 | public pluralize?: boolean, 237 | public tableCaseConvention?: CaseChange, 238 | public fieldCaseConvention?: CaseChange, 239 | public enumCaseConvention?: CaseChange, 240 | public mapTableCaseConvention?: CaseChange, 241 | public mapFieldCaseConvention?: CaseChange, 242 | public mapEnumCaseConvention?: CaseChange, 243 | protected children?: Record, 244 | ) {} 245 | 246 | public isDisabled(...scope: string[]) { 247 | return this._recurse('disable', scope) ?? this.disable; 248 | } 249 | public isPlural(...scope: string[]) { 250 | return this._recurse('pluralize', scope) ?? this.pluralize; 251 | } 252 | public table(...scope: string[]) { 253 | return this._recurse('tableCaseConvention', scope) ?? this.tableCaseConvention ?? DEFAULTS.tableCaseConvention; 254 | } 255 | public mapTable(...scope: string[]) { 256 | return this._recurse('mapTableCaseConvention', scope) ?? this.mapTableCaseConvention; 257 | } 258 | public enum(...scope: string[]) { 259 | return this._recurse('enumCaseConvention', scope) ?? this.enumCaseConvention ?? this._recurse('tableCaseConvention', scope) ?? DEFAULTS.enumCaseConvention; 260 | } 261 | public mapEnum(...scope: string[]) { 262 | return this._recurse('mapEnumCaseConvention', scope) ?? this.mapEnumCaseConvention ?? this._recurse('mapTableCaseConvention', scope) ?? this.mapTableCaseConvention; 263 | } 264 | public field(...scope: string[]) { 265 | return this._recurse('fieldCaseConvention', scope) ?? this.fieldCaseConvention ?? DEFAULTS.fieldCaseConvention!; 266 | } 267 | public mapField(...scope: string[]) { 268 | return this._recurse('mapFieldCaseConvention', scope) ?? this.mapFieldCaseConvention; 269 | } 270 | private _recurse(k: K, [next, ...rest]: string[]): CaseConventions[K] | undefined { 271 | if (!next) { 272 | return (this as any)[k]; 273 | } 274 | if (this.children?.hasOwnProperty(next)) { 275 | return this.children[next]._recurse(k, rest); 276 | } 277 | if (this.children?.hasOwnProperty(pascalCase(next))) { 278 | return this.children[pascalCase(next)]._recurse(k, rest); 279 | } 280 | if (this.children?.hasOwnProperty(snakeCase(next))) { 281 | return this.children[snakeCase(next)]._recurse(k, rest); 282 | } 283 | if (this.children?.hasOwnProperty(camelCase(next))) { 284 | return this.children[camelCase(next)]._recurse(k, rest); 285 | } 286 | 287 | const haystack = this.children ?? {}; 288 | for (const key in haystack) { 289 | const regex = new RegExp('$' + key + '^'); 290 | if (regex.test(next)) { 291 | return this.children![key]._recurse(k, rest); 292 | } 293 | if (regex.test(pascalCase(next))) { 294 | return this.children![pascalCase(next)]._recurse(k, rest); 295 | } 296 | if (regex.test(snakeCase(next))) { 297 | return this.children![snakeCase(next)]._recurse(k, rest); 298 | } 299 | if (regex.test(camelCase(next))) { 300 | return this.children![camelCase(next)]._recurse(k, rest); 301 | } 302 | } 303 | return undefined; 304 | } 305 | } 306 | 307 | export type CaseConventions = { 308 | tableCaseConvention: CaseChange; 309 | fieldCaseConvention: CaseChange; 310 | enumCaseConvention?: CaseChange; 311 | mapTableCaseConvention?: CaseChange; 312 | mapFieldCaseConvention?: CaseChange; 313 | mapEnumCaseConvention?: CaseChange; 314 | pluralize?: boolean; 315 | disable?: boolean; 316 | }; 317 | 318 | export type UnmanagedConventions = 'disabled'; 319 | 320 | export type ScopeConventions = 321 | | CaseConventions 322 | | UnmanagedConventions 323 | ; 324 | 325 | export const DEFAULTS = { 326 | tableCaseConvention: pascalCase, 327 | fieldCaseConvention: camelCase, 328 | enumCaseConvention: pascalCase, 329 | }; 330 | 331 | export function defaultConventions() { 332 | return { ...DEFAULTS }; 333 | } 334 | 335 | function imbueWithNextAuth(content: ConventionFile): ConventionFile { 336 | content.override = content.override ?? {}; 337 | 338 | content.override.Account = { 339 | default: 'table=pascal; mapTable=pascal;', 340 | field: { 341 | id: 'field=camel; mapField=camel', 342 | userId: 'field=camel; mapField=camel', 343 | type: 'field=camel; mapField=camel', 344 | provider: 'field=camel; mapField=camel', 345 | providerAccountId: 'field=camel; mapField=camel', 346 | refresh_token: 'field=snake; mapField=snake', 347 | access_token: 'field=snake; mapField=snake', 348 | expires_at: 'field=snake; mapField=snake', 349 | token_type: 'field=snake; mapField=snake', 350 | scope: 'field=snake; mapField=snake', 351 | id_token: 'field=snake; mapField=snake', 352 | session_state: 'field=snake; mapField=snake', 353 | user: 'field=snake; mapField=snake', 354 | } 355 | }; 356 | content.override.Session = { 357 | default: 'table=pascal; mapTable=pascal; field=camel; mapField=camel' 358 | }; 359 | content.override.User = { 360 | default: 'table=pascal; mapTable=pascal; field=camel; mapField=camel' 361 | }; 362 | content.override.VerificationToken = { 363 | default: 'table=pascal; mapTable=pascal; field=camel; mapField=camel' 364 | }; 365 | 366 | return content; 367 | } 368 | -------------------------------------------------------------------------------- /src/convention-transformer.ts: -------------------------------------------------------------------------------- 1 | import { plural } from 'pluralize'; 2 | import { ConventionStore } from './convention-store'; 3 | 4 | const MODEL_TOKEN = 'model'; 5 | const VIEW_TOKEN = 'view'; 6 | const ENUM_TOKEN = 'enum'; 7 | 8 | export type CaseChange = (input: string) => string; 9 | 10 | export function isPrimitive(field_type: string) { 11 | field_type = field_type.replace('[]', '').replace('?', '').replace(/("\w+")/, ''); 12 | field_type = field_type.split('(')[0]; 13 | return [ 14 | 'String', 15 | 'Boolean', 16 | 'Int', 17 | 'BigInt', 18 | 'Float', 19 | 'Decimal', 20 | 'DateTime', 21 | 'Json', 22 | 'Bytes', 23 | 'Unsupported', 24 | ].includes(field_type); 25 | } 26 | 27 | const MODEL_DECLARATION_REGEX = /^\s*(model|view)\s+(?\w+)\s*\{\s*/; 28 | const ENTITY_MAP_ANNOTATION_REGEX = /@@map\("(?.+)"\)/; 29 | const ENUM_DECLARATION_REGEX = /^\s*enum\s+(?\w+)\s*\{\s*/; 30 | const FIELD_DECLARATION_REGEX = /^(\s*)(?\w+)(\s+)(?[\w+]+(\((?:[\w\s"'.]+(?:,\s*)?)*\))?)(?[\[\]\?]*)(\s+.*\s*)?(?\/\/.*)?/; 31 | const MAP_ANNOTATION_REGEX = /@map\("(?.+)"\)/; 32 | const RELATION_ANNOTATION_REGEX = /(?@relation\("?\w*"?,?\s*)((?(fields|references):\s*\[)(?\w+(,\s*\w+\s*)*))((?\]\,\s*(fields|references):\s*\[)(?\w+(,\s*\w+\s*)*))(?\].*)/; 33 | const EZ_TABLE_INDEX_REGEX = /\@\@index\((?\[[\w\s,]+\])/; 34 | const CPLX_TABLE_INDEX_REGEX = /\@\@index.*/; 35 | const TABLE_UNIQUE_REGEX = /\@\@unique\((?\[[\w\s,]+\])/; 36 | const TABLE_ID_REGEX = /\@\@id\((?\[[\w\s,]+\])/; 37 | 38 | export class ConventionTransformer { 39 | public static migrateCaseConventions(file_contents: string, store: ConventionStore): [string?, Error?] { 40 | const lines = file_contents.split('\n'); 41 | 42 | const [reshape_model_error] = ConventionTransformer.reshapeModelDefinitions(lines, store); 43 | if (reshape_model_error) { 44 | return [, reshape_model_error]; 45 | } 46 | 47 | const [reshaped_enum_map, reshape_enum_error] = ConventionTransformer.getEnumNameMap(lines, store); 48 | if (reshape_enum_error) { 49 | return [, reshape_enum_error]; 50 | } 51 | 52 | const [reshape_model_field_error] = ConventionTransformer.reshapeModelFields(lines, store, reshaped_enum_map!); 53 | if (reshape_model_field_error) { 54 | return [, reshape_model_field_error]; 55 | } 56 | 57 | const [enum_reshape_error] = ConventionTransformer.reshapeEnumDefinitions(lines, store); 58 | if (enum_reshape_error) { 59 | return [, enum_reshape_error]; 60 | } 61 | 62 | return [lines!.join('\n'),]; 63 | } 64 | 65 | private static findExistingMapAnnotation(lines: string[]): [string | undefined, number] { 66 | for (let i = lines.length - 1; i >= 0; i--) { 67 | const line = lines[i]; 68 | const matches = line.match(ENTITY_MAP_ANNOTATION_REGEX); 69 | if (!!matches) { 70 | return [matches.groups!['map'], i]; 71 | } 72 | } 73 | return [, -1]; 74 | } 75 | 76 | private static reshapeModelDefinitions(lines: string[], store: ConventionStore): [Error?] { 77 | const [model_bounds, model_bounds_error] = ConventionTransformer.getDefinitionBounds([MODEL_TOKEN, VIEW_TOKEN], lines); 78 | if (model_bounds_error) { 79 | return [model_bounds_error]; 80 | } 81 | /* 82 | * Applies the following invariant across all model names: 83 | * - tableCaseConvention(curr_model_name) is used for the declaration of the model 84 | * - mapTableCase(curr_model_name) or current value is used for the @@map annotation, 85 | * unless that would equal tableCaseConvention(curr_model_name) 86 | */ 87 | let offset = 0; 88 | for (let [base_start, base_end] of model_bounds!) { 89 | const start = base_start + offset; 90 | const end = base_end + offset; 91 | try { 92 | const model_declaration_line = MODEL_DECLARATION_REGEX.exec(lines[start]); 93 | const [existing_db_model_name, map_anno_index] = ConventionTransformer.findExistingMapAnnotation(lines.slice(start, end)); 94 | const curr_model_name = model_declaration_line!.groups!['model']; 95 | if (store.isDisabled(curr_model_name)) { 96 | continue; 97 | } 98 | const conv_model_name = store.table(curr_model_name)(curr_model_name); 99 | const db_model_name = store.mapTable?.(curr_model_name)?.(curr_model_name) ?? existing_db_model_name ?? curr_model_name; 100 | const map_anno_line_no = start + map_anno_index; 101 | if (conv_model_name == db_model_name && 0 <= map_anno_index) { 102 | lines.splice(map_anno_line_no, 1); 103 | offset -= 1; 104 | } 105 | if (conv_model_name !== curr_model_name) { 106 | lines[start] = lines[start].replace(curr_model_name, conv_model_name); 107 | } 108 | if (conv_model_name !== db_model_name) { 109 | const map_model_line = ` @@map("${db_model_name}")`; 110 | if (0 <= map_anno_index) { 111 | lines.splice(map_anno_line_no, 1, map_model_line); 112 | } else { 113 | lines.splice(start + 1, 0, map_model_line); 114 | offset += 1; 115 | } 116 | } 117 | } catch (error) { 118 | return [error as Error]; 119 | } 120 | } 121 | 122 | return []; 123 | } 124 | 125 | private static getEnumNameMap(lines: string[], store: ConventionStore): [Map?, Error?] { 126 | const reshaped_enum_map = new Map(); // Map 127 | 128 | const [enum_bounds, enum_bounds_error] = ConventionTransformer.getDefinitionBounds([ENUM_TOKEN], lines); 129 | if (enum_bounds_error) { 130 | return [, enum_bounds_error]; 131 | } 132 | 133 | let offset = 0; 134 | for (const [base_start] of enum_bounds!) { 135 | const start = base_start + offset; 136 | try { 137 | const enum_declaration_line = ENUM_DECLARATION_REGEX.exec(lines[start]); 138 | const raw_enum_name = enum_declaration_line!.groups!['enum']; 139 | const reshaped_enum_name = store.enum(raw_enum_name)?.(raw_enum_name); 140 | reshaped_enum_map.set(raw_enum_name, reshaped_enum_name); 141 | } catch (error) { 142 | return [, error as Error]; 143 | } 144 | } 145 | 146 | return [reshaped_enum_map]; 147 | } 148 | 149 | private static reshapeEnumDefinitions(lines: string[], store: ConventionStore): [Error?] { 150 | const [enum_bounds, err] = ConventionTransformer.getDefinitionBounds([ENUM_TOKEN], lines); 151 | if (err) { 152 | return [err]; 153 | } 154 | /* 155 | * Applies the following invariant across all enum names: 156 | * - enumCaseConvention(curr_enum_name) is used for the declaration of the enum 157 | * - mapEnumCase(curr_enum_name) or current value is used for the @@map annotation, 158 | * unless that would equal tableCaseConvention(curr_enum_name) 159 | */ 160 | let offset = 0; 161 | for (let [base_start, base_end] of enum_bounds!) { 162 | const start = base_start + offset; 163 | const end = base_end + offset; 164 | try { 165 | const enum_declaration_line = ENUM_DECLARATION_REGEX.exec(lines[start]); 166 | const [existing_db_enum_name, map_anno_idx] = ConventionTransformer.findExistingMapAnnotation(lines.slice(start, end)); 167 | const curr_enum_name = enum_declaration_line!.groups!['enum']; 168 | if (store.isDisabled(curr_enum_name)) { 169 | continue; 170 | } 171 | const conv_enum_name = store.enum(curr_enum_name)(curr_enum_name); 172 | const db_enum_name = store.mapEnum?.(curr_enum_name)?.(curr_enum_name) ?? existing_db_enum_name ?? curr_enum_name; 173 | const map_anno_line_no = start + map_anno_idx; 174 | if (conv_enum_name == db_enum_name && 0 <= map_anno_idx) { 175 | lines.splice(map_anno_line_no, 1); 176 | offset -= 1; 177 | } 178 | if (conv_enum_name !== curr_enum_name) { 179 | lines[start] = lines[start].replace(curr_enum_name, conv_enum_name); 180 | } 181 | if (conv_enum_name !== db_enum_name) { 182 | const map_enum_line = ` @@map("${db_enum_name}")`; 183 | if (0 <= map_anno_idx) { 184 | lines.splice(map_anno_line_no, 1, map_enum_line); 185 | } else { 186 | lines.splice(start + 1, 0, map_enum_line); 187 | offset += 1; 188 | } 189 | } 190 | } catch (error) { 191 | return [error as Error]; 192 | } 193 | } 194 | 195 | return []; 196 | } 197 | 198 | private static reshapeModelFields(lines: string[], store: ConventionStore, reshaped_enum_map: Map): [Error?] { 199 | const [model_bounds, model_bounds_error] = ConventionTransformer.getDefinitionBounds([MODEL_TOKEN, VIEW_TOKEN], lines); 200 | if (model_bounds_error) { 201 | return [model_bounds_error]; 202 | } 203 | 204 | /* 205 | * Applies the following invariant across all model/view field names: 206 | * - fieldCaseConvention(curr_field_name) is used for the declaration of the model 207 | * - mapFieldCase(curr_field_name) or current value is used for the @map annotation, 208 | * unless that would equal fieldCaseConvention(curr_field_name) 209 | */ 210 | for (const [start, end] of model_bounds!) { 211 | const context = MODEL_DECLARATION_REGEX.exec(lines[start])?.groups?.['model']; 212 | if (store.isDisabled(context!)) { 213 | continue; 214 | } 215 | for (let i = start+1; i < end; i++) { 216 | const field_declaration_line = FIELD_DECLARATION_REGEX.exec(lines[i]); 217 | if (field_declaration_line) { 218 | let [search_term, chunk0, field, chunk2, type, _unused, is_array_or_nullable, chunk5] = field_declaration_line; 219 | if (store.isDisabled(context!, field)) { 220 | continue; 221 | } 222 | const og_db_field_name: string | undefined = MAP_ANNOTATION_REGEX.exec(chunk5)?.groups?.['map']; 223 | const enum_name = reshaped_enum_map.get(type); 224 | if (og_db_field_name) { 225 | // delete it, we'll tee it up again in a moment 226 | chunk5 = chunk5.replace(`@map("${og_db_field_name}")`, ''); 227 | } 228 | let conv_field_name = store.field(context!, field)(field); 229 | const db_field_name = store.mapField?.(context!, conv_field_name)?.(conv_field_name) ?? og_db_field_name ?? field; 230 | if (store.isPlural(context!, field) && is_array_or_nullable.startsWith('[]')) { 231 | conv_field_name = plural(conv_field_name); 232 | } 233 | let map_anno_fragment = ''; 234 | 235 | // Primitive type 236 | if ( 237 | conv_field_name !== db_field_name && 238 | !lines[i].includes('@relation') && 239 | isPrimitive(type) 240 | ) { 241 | map_anno_fragment = ` @map("${db_field_name}")`; 242 | } 243 | // Enum type 244 | else if (enum_name) { 245 | type = enum_name; 246 | map_anno_fragment = ` @map("${db_field_name}")`; 247 | } 248 | // User type 249 | else if (!isPrimitive(type)) { 250 | type = store.table(context!)(type) ?? type ; 251 | } 252 | 253 | if (conv_field_name === db_field_name) { 254 | map_anno_fragment = ''; 255 | } 256 | 257 | lines[i] = lines[i].replace(search_term, [chunk0, conv_field_name, chunk2, type, is_array_or_nullable, map_anno_fragment, chunk5,].join('')); 258 | } 259 | 260 | const relation_annotation_line = RELATION_ANNOTATION_REGEX.exec(lines[i]); 261 | if (relation_annotation_line) { 262 | // , chunk0, fields, chunk2, references, chunk4 263 | const [search_term] = relation_annotation_line!; 264 | const { preamble, cue1, ids1, cue2, ids2, trailer } = relation_annotation_line!.groups!; 265 | const updated_ids1 = ids1 266 | .split(/,\s*/) 267 | .map(e => store.field(context!, e)(e)) 268 | .join(', '); 269 | const updated_ids2 = ids2 270 | .split(/,\s*/) 271 | .map(e => store.field(context!, e)(e)) 272 | .join(', '); 273 | lines[i] = lines[i].replace(search_term, [preamble, cue1, updated_ids1, cue2, updated_ids2, trailer].join('')); 274 | 275 | } 276 | const table_unique_declaration_line = TABLE_UNIQUE_REGEX.exec(lines[i]); 277 | if (table_unique_declaration_line) { 278 | const field_names = table_unique_declaration_line!.groups!['fields']; 279 | const updated_field_names = `[${field_names.split(/,\s*/).map(e => store.field(context!, e)(e)).join(', ')}]`; 280 | lines[i] = lines[i].replace(field_names, updated_field_names); 281 | } 282 | 283 | let table_index_declaration_line = EZ_TABLE_INDEX_REGEX.exec(lines[i]); 284 | if (table_index_declaration_line) { 285 | const field_names = table_index_declaration_line!.groups!['fields']; 286 | const updated_field_names = `[${field_names.split(/,\s*/).map(e => store.field(context!, e)(e)).join(', ')}]`; 287 | lines[i] = lines[i].replace(field_names, updated_field_names); 288 | } 289 | table_index_declaration_line = 290 | table_index_declaration_line ? null : CPLX_TABLE_INDEX_REGEX.exec(lines[i]); 291 | if (table_index_declaration_line) { 292 | const field_names = [...new Set([ 293 | ...ConventionTransformer.getFieldNames(lines[i], ConventionTransformer.DEFAULT_START_POS), 294 | ...ConventionTransformer.getFieldNames(lines[i], ConventionTransformer.FIELDS_START_POS), 295 | ...ConventionTransformer.getFieldNames(lines[i], ConventionTransformer.REF_START_POS), 296 | ])]; 297 | lines[i] = field_names.reduce( 298 | (line, next) => line.replace(next, store.field(context!, next)(next)), 299 | lines[i] 300 | ); 301 | } 302 | 303 | 304 | const table_id_declaration_line = TABLE_ID_REGEX.exec(lines[i]); 305 | if (table_id_declaration_line) { 306 | const field_names = table_id_declaration_line!.groups!['fields']; 307 | const updated_field_names = `[${field_names.split(/,\s*/).map(e => store.field(context!, e)(e)).join(', ')}]`; 308 | lines[i] = lines[i].replace(field_names, updated_field_names); 309 | } 310 | } 311 | } 312 | 313 | return []; 314 | } 315 | 316 | private static DEFAULT_START_POS = 'DEFAULT'; 317 | private static FIELDS_START_POS = 'fields'; 318 | private static REF_START_POS = 'references'; 319 | 320 | private static getFieldNames(haystack: string, start_position: string): string[] { 321 | if (start_position === ConventionTransformer.DEFAULT_START_POS) { 322 | let i = 0; 323 | while (haystack[i++] !== '['); 324 | let substr = ''; 325 | while (haystack[i] !== ']') { 326 | substr += haystack[i++]; 327 | } 328 | return ConventionTransformer.stripProperties(substr).split(/\s*,\s*/); 329 | } 330 | const regex = new RegExp(`${start_position}\\s*:\\s*\\[([^\\]]+)\\]`, 'g'); 331 | const matches = regex.exec(haystack); 332 | 333 | return matches ? (ConventionTransformer.stripProperties(matches[1]).split(/\s*[,]\s*/)) : []; 334 | } 335 | 336 | private static stripProperties(haystack: string): string { 337 | let out_str = ''; 338 | let balance = 0; 339 | for (let i = 0; i < haystack.length; i++) { 340 | if (haystack[i] === '(') { 341 | balance += 1; 342 | } 343 | if (balance === 0) { 344 | out_str += haystack[i]; 345 | } 346 | if (haystack[i] === ')') { 347 | balance -= 1; 348 | } 349 | } 350 | return out_str; 351 | } 352 | 353 | private static getDefinitionBounds(tokens: string[], lines: string[]): [[number, number][]?, Error?] { 354 | const END_DEFINITION_TOKEN = '}'; 355 | 356 | const definition_bounds: [number, number][] = []; 357 | let within_definition = false; 358 | let boundary_cursor: [number, number] = [] as any; 359 | for (const token of tokens) { 360 | for (const [index, line] of lines.entries()) { 361 | if (!within_definition && line.startsWith(token)) { 362 | boundary_cursor.push(index); 363 | within_definition = true; 364 | } else if (within_definition && line.trim().endsWith(END_DEFINITION_TOKEN)) { 365 | boundary_cursor.push(index); 366 | definition_bounds.push(boundary_cursor); 367 | boundary_cursor = [] as any; 368 | within_definition = false; 369 | } 370 | } 371 | if (within_definition) { 372 | return [, new Error(`${token} starting on line ${boundary_cursor[0]} did not end`)]; 373 | } 374 | } 375 | return [definition_bounds.sort(([start_a], [start_b]) => start_a - start_b),]; 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /test/__fixtures__/cascading-deletion-and-fk.schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "postgresql" 3 | url = env("DATABASE_URL") 4 | } 5 | 6 | generator client { 7 | provider = "prisma-client-js" 8 | } 9 | 10 | model projects { 11 | id Int @id @default(autoincrement()) 12 | name String? @db.VarChar 13 | jira_issues jira_issues[] 14 | } 15 | 16 | model jira_issues { 17 | id Int @id @default(autoincrement()) 18 | jira_integration_id Int? 19 | project_id Int 20 | projects projects? @relation(fields: [project_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "jira_issues_projects_fkey") 21 | } 22 | 23 | model field_key { 24 | key String 25 | type String // str, num, bool 26 | form_id String @db.Uuid 27 | form Form @relation(references: [id], fields: [form_id], onDelete: Cascade) 28 | 29 | @@id([key, form_id]) 30 | } 31 | -------------------------------------------------------------------------------- /test/__fixtures__/columns-with-view-in-name.schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "sqlite" 3 | url = "file:database.db" 4 | } 5 | 6 | // generator 7 | generator client { 8 | provider = "prisma-client-js" 9 | } 10 | 11 | model Demo { 12 | view_count Int 13 | } 14 | -------------------------------------------------------------------------------- /test/__fixtures__/comments-on-model-lines.schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "postgresql" 3 | url = env("DATABASE_URL") 4 | } 5 | 6 | generator js_cli { 7 | provider = "prisma-client-js" 8 | } 9 | 10 | model a_model { 11 | id String @id @default(uuid()) @db.Uuid 12 | name String @unique 13 | field_with_comments String? // This should not break our ability to insert map annotations 14 | } 15 | -------------------------------------------------------------------------------- /test/__fixtures__/complex-index.schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "mysql" 3 | url = "file:database.db" 4 | } 5 | 6 | // generator 7 | generator client { 8 | provider = "prisma-client-js" 9 | } 10 | 11 | model Post { 12 | id Int @id @default(autoincrement()) 13 | title_pain String 14 | content_hash String? 15 | 16 | @@index(fields: [title_pain, content_hash(length: 12)], name: "main_index") 17 | } 18 | 19 | model Address { 20 | id Int @id 21 | street String 22 | number Int 23 | User User[] 24 | } 25 | 26 | model User { 27 | id Int @id 28 | email String 29 | address Address @relation(fields: [address_id], references: [id]) 30 | address_id Int 31 | 32 | @@index([address_id], map: "an_index_name") 33 | } 34 | -------------------------------------------------------------------------------- /test/__fixtures__/disable.prisma-case-format: -------------------------------------------------------------------------------- 1 | override: 2 | mYMoDeL: 'disable' 3 | partially_maintained: 4 | default: 'table=pascal; mapTable=snake' 5 | field: 6 | maintained_property: 'field=pascal' 7 | unmaintained_property: 'disable' 8 | divergent_convention: 'mapField=!SeeItsACompletelyDifferentName' 9 | AmazingModel: 10 | default: 'table=pascal; mapTable=!Amaze_o' 11 | field: 12 | fuzz: 'mapField=!fizz_buzz' 13 | taxis: 14 | default: 'mapTable=!Cabs' 15 | taxes: 16 | default: 'mapTable=!tax_codes' -------------------------------------------------------------------------------- /test/__fixtures__/disable.schema.prisma: -------------------------------------------------------------------------------- 1 | model mYMoDeL { 2 | lMfAo String @id @map("ToXiC_DeV_____WaS_HeeeEeEeRe_ayY_lMao") 3 | i_h8_ur_company Json 4 | 5 | @@map("so_uncalled_for_dot_gov") 6 | } 7 | 8 | model partially_maintained { 9 | id String @id 10 | 11 | maintained_property String 12 | unmaintained_property String 13 | divergent_convention Int 14 | } 15 | 16 | model Pristine { 17 | id Int @id 18 | data String @unique 19 | convert_this_pls DateTime 20 | } 21 | 22 | model amazing_model { 23 | id Int @id 24 | fuzz String 25 | } 26 | 27 | enum Taxis { 28 | Yellow 29 | Uber 30 | } 31 | 32 | enum Taxes { 33 | TaxCode1 34 | TaxCode2 35 | 36 | @@map("tax_codes") 37 | } -------------------------------------------------------------------------------- /test/__fixtures__/disable2.prisma-case-format: -------------------------------------------------------------------------------- 1 | default: 'field=camel; table=pascal; enum=pascal; pluralize=true' 2 | override: 3 | Model: 4 | field: 5 | passwordsSingular: 'mapField=snake,singular' 6 | requestsSingular: 'mapField=!requests_singular' 7 | columnsSingular: 'mapField=snake; pluralize=disable' 8 | plurals: 'mapField=snake,singular' -------------------------------------------------------------------------------- /test/__fixtures__/disable2.schema.prisma: -------------------------------------------------------------------------------- 1 | model Model { 2 | id String @id @default(cuid()) 3 | passwordsSingular String[] 4 | requestsSingular String[] 5 | columnsSingular String[] 6 | pluralsPlural String[] 7 | plural String[] 8 | } 9 | -------------------------------------------------------------------------------- /test/__fixtures__/enum-tables-map.schema.prisma: -------------------------------------------------------------------------------- 1 | enum UserLocale { 2 | fr_FR 3 | en_EN 4 | } 5 | 6 | model User { 7 | id Int @id @default(autoincrement()) 8 | email String @unique 9 | firstName String @default("") @map("first_name") 10 | lastName String @default("") @map("last_name") 11 | locale UserLocale @default(fr_FR) @map("locale") 12 | 13 | @@map("user") 14 | } -------------------------------------------------------------------------------- /test/__fixtures__/enum.schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "mysql" 3 | url = env("DATABASE_URL") 4 | } 5 | 6 | generator client { 7 | provider = "prisma-client-js" 8 | } 9 | 10 | model posts { 11 | id Int @id @default(autoincrement()) 12 | content String? @db.VarChar(245) 13 | postType post_type 14 | } 15 | 16 | enum post_type { 17 | Note 18 | Question 19 | } 20 | 21 | enum snaggle_flark { 22 | foo 23 | buz 24 | bazz 25 | } 26 | 27 | enum AppleColors { 28 | green 29 | red 30 | } 31 | -------------------------------------------------------------------------------- /test/__fixtures__/idempotency.schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "sqlite" 3 | url = "file:database.db" 4 | } 5 | 6 | // generator 7 | generator client { 8 | provider = "prisma-client-js" 9 | } 10 | 11 | model DemoIt { 12 | id Int @id 13 | data_field String 14 | } 15 | -------------------------------------------------------------------------------- /test/__fixtures__/issue.schema.prisma: -------------------------------------------------------------------------------- 1 | model sessions { 2 | session_id String @id @localContainer.VarChar(128) 3 | expires Int @localContainer.UnsignedInt 4 | data String? @localContainer.Text 5 | } -------------------------------------------------------------------------------- /test/__fixtures__/issue2.schema.prisma: -------------------------------------------------------------------------------- 1 | model Account { 2 | id String @id @default(cuid()) 3 | userId String @map("user_id") 4 | providerType String @map("provider_type") 5 | providerId String @map("provider_id") 6 | providerAccountId String 7 | refreshToken String? @map("refresh_token") 8 | accessToken String? @map("access_token") 9 | accessTokenExpires DateTime? @map("access_token_expires") 10 | createdAt DateTime @default(now()) @map("created_at") 11 | updatedAt DateTime @updatedAt @map("updated_at") 12 | user User @relation(fields: [userId], references: [id]) 13 | 14 | @@unique([providerId, providerAccountId]) 15 | @@map("account") 16 | } 17 | 18 | model User { 19 | id String @id 20 | accounts Account[] 21 | } -------------------------------------------------------------------------------- /test/__fixtures__/model-columns-with-underscores.schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "sqlite" 3 | url = "file:database.db" 4 | } 5 | 6 | // generator 7 | generator client { 8 | provider = "prisma-client-js" 9 | } 10 | 11 | model Demo { 12 | article_id Int 13 | } 14 | -------------------------------------------------------------------------------- /test/__fixtures__/next-auth.schema.prisma: -------------------------------------------------------------------------------- 1 | model my_model { 2 | id String @id @default(uuid()) 3 | data_field String 4 | } 5 | 6 | model Account { 7 | id String @id @default(cuid()) 8 | userId String 9 | type String 10 | provider String 11 | providerAccountId String 12 | refresh_token String? @db.Text 13 | access_token String? @db.Text 14 | expires_at Int? 15 | token_type String? 16 | scope String? 17 | id_token String? @db.Text 18 | session_state String? 19 | 20 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 21 | 22 | @@unique([provider, providerAccountId]) 23 | } 24 | 25 | model Session { 26 | id String @id @default(cuid()) 27 | sessionToken String @unique 28 | userId String 29 | expires DateTime 30 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 31 | } 32 | 33 | model User { 34 | id String @id @default(cuid()) 35 | name String? 36 | email String? @unique 37 | emailVerified DateTime? 38 | image String? 39 | accounts Account[] 40 | sessions Session[] 41 | } 42 | 43 | model VerificationToken { 44 | identifier String 45 | token String @unique 46 | expires DateTime 47 | 48 | @@unique([identifier, token]) 49 | } -------------------------------------------------------------------------------- /test/__fixtures__/pluralize-fields.schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "sqlite" 3 | url = env("DATABASE_URL") 4 | } 5 | 6 | generator client { 7 | provider = "prisma-client-js" 8 | } 9 | 10 | model Business { 11 | id String @id(map: "BusinessId") @default(dbgenerated("gen_random_uuid()")) @db.Uuid 12 | languageCode String @db.Char(2) 13 | currencyCode String @db.Char(3) 14 | createdAt DateTime @default(now()) @db.Timestamptz(0) 15 | name String @db.VarChar(64) 16 | language String[] @default([]) @db.Char(2) 17 | phone String @db.VarChar(15) 18 | address String? @db.VarChar(250) 19 | billingName String? @db.VarChar(64) 20 | billingAddress String? @db.VarChar(250) 21 | taxOffice String? @db.VarChar(64) 22 | taxId String? @db.VarChar(64) 23 | defaultTaxRate Decimal? @db.Decimal(8, 6) 24 | isActive Boolean @default(true) 25 | batchOperation BatchOperation[] 26 | currency Currency @relation(fields: [currencyCode], references: [code], onUpdate: Restrict, map: "BusinessCurrency") 27 | language Language @relation(fields: [languageCode], references: [code], onUpdate: Restrict, map: "BusinessLanguage") 28 | ingredientCategory IngredientCategory[] 29 | itemCategory ItemCategory[] 30 | optionCategory OptionCategory[] 31 | profile Profile[] 32 | recipe Recipe[] 33 | tab Tab[] 34 | targetGroup TargetGroup[] 35 | 36 | @@schema("public") 37 | } 38 | -------------------------------------------------------------------------------- /test/__fixtures__/readme-demo.schema.prisma: -------------------------------------------------------------------------------- 1 | model house_rating { 2 | id Int @id @default(autoincrement()) 3 | house_id String 4 | house house @relation(fields: [house_id], references: [id]) 5 | } 6 | 7 | model house { 8 | id String @id @default(uuid()) // comments 9 | house_rating house_rating[] // do comments get 10 | } 11 | -------------------------------------------------------------------------------- /test/__fixtures__/tables-with-pluralized-db-targets.schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "sqlite" 3 | url = "file:database.db" 4 | } 5 | 6 | // generator 7 | generator client { 8 | provider = "prisma-client-js" 9 | } 10 | 11 | model Users { 12 | id Int @id 13 | name String 14 | 15 | @@map("users") 16 | } 17 | 18 | model Group { 19 | id Int @id 20 | name String 21 | } 22 | 23 | model Sisters { 24 | id Int @id 25 | name String 26 | 27 | @@map("sisters") 28 | } 29 | 30 | model Brothers { 31 | id Int @id 32 | name String 33 | } 34 | 35 | -------------------------------------------------------------------------------- /test/__fixtures__/views.schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | output = "../node_modules/@generated/read" 4 | previewFeatures = ["views"] 5 | } 6 | 7 | generator typegraphql { 8 | provider = "typegraphql-prisma" 9 | } 10 | 11 | datasource db { 12 | provider = "postgres" 13 | url = env("DB_URL") 14 | } 15 | 16 | model goose_db_version { 17 | id Int @id @default(autoincrement()) 18 | version_id BigInt 19 | is_applied Boolean 20 | tstamp DateTime? @default(now()) @db.Timestamp(6) 21 | circle_value Unsupported("circle")? 22 | } 23 | 24 | view accounts { 25 | id String @unique @db.Uuid 26 | created_at DateTime? @db.Timestamp(6) 27 | name String? 28 | updated_at DateTime? @db.Timestamp(6) 29 | owned_by String? @db.Uuid 30 | users user[] 31 | } 32 | 33 | model user { 34 | id String @id 35 | name String 36 | accounts accounts? @relation(fields: [accounts_id], references: [id]) 37 | accounts_id String? @db.Uuid 38 | } 39 | -------------------------------------------------------------------------------- /test/__snapshots__/convention-transformer.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`disable2 1`] = ` 4 | "model Model { 5 | id String @id @default(cuid()) 6 | passwordsSingular String[] @map("passwords_singular") 7 | requestsSingulars String[] @map("requests_singular") 8 | columnsSingular String[] 9 | pluralsPlurals String[] @map("pluralsPlural") 10 | } 11 | " 12 | `; 13 | 14 | exports[`if next-auth is marked as in use, those models should be managed separately/consistently 1`] = ` 15 | "model MyModel { 16 | id String @id @default(uuid()) 17 | dataField String @map("data_field") 18 | 19 | @@map("my_model") 20 | } 21 | 22 | model Account { 23 | id String @id @default(cuid()) 24 | userId String 25 | type String 26 | provider String 27 | providerAccountId String 28 | refresh_token String? @db.Text 29 | access_token String? @db.Text 30 | expires_at Int? 31 | token_type String? 32 | scope String? 33 | id_token String? @db.Text 34 | session_state String? 35 | 36 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 37 | 38 | @@unique([provider, providerAccountId]) 39 | } 40 | 41 | model Session { 42 | id String @id @default(cuid()) 43 | sessionToken String @unique 44 | userId String 45 | expires DateTime 46 | user User @relation(fields: [userId], references: [id], onDelete: Cascade) 47 | } 48 | 49 | model User { 50 | id String @id @default(cuid()) 51 | name String? 52 | email String? @unique 53 | emailVerified DateTime? 54 | image String? 55 | accounts Account[] 56 | sessions Session[] 57 | } 58 | 59 | model VerificationToken { 60 | identifier String 61 | token String @unique 62 | expires DateTime 63 | 64 | @@unique([identifier, token]) 65 | } 66 | " 67 | `; 68 | 69 | exports[`if the entity is marked disabled, or is marked with !, formatting is applied specially 1`] = ` 70 | "model mYMoDeL { 71 | lMfAo String @id @map("ToXiC_DeV_____WaS_HeeeEeEeRe_ayY_lMao") 72 | i_h8_ur_company Json 73 | 74 | @@map("so_uncalled_for_dot_gov") 75 | } 76 | 77 | model PartiallyMaintained { 78 | id String @id 79 | 80 | MaintainedProperty String @map("maintained_property") 81 | unmaintained_property String 82 | divergentConvention Int @map("SeeItsACompletelyDifferentName") 83 | 84 | @@map("partially_maintained") 85 | } 86 | 87 | model Pristine { 88 | id Int @id 89 | data String @unique 90 | convertThisPls DateTime @map("convert_this_pls") 91 | } 92 | 93 | model AmazingModel { 94 | id Int @id 95 | fuzz String @map("fizz_buzz") 96 | 97 | @@map("Amaze_o") 98 | } 99 | 100 | enum Taxis { 101 | Yellow 102 | Uber 103 | 104 | @@map("Cabs") 105 | } 106 | 107 | enum Taxes { 108 | TaxCode1 109 | TaxCode2 110 | 111 | @@map("tax_codes") 112 | } 113 | " 114 | `; 115 | 116 | exports[`issue 1`] = ` 117 | "model Sessions { 118 | sessionId String @id @map("session_id") @localContainer.VarChar(128) 119 | expires Int @localContainer.UnsignedInt 120 | data String? @localContainer.Text 121 | 122 | @@map("sessions") 123 | } 124 | " 125 | `; 126 | 127 | exports[`issue2 1`] = ` 128 | "model Account { 129 | id String @id @default(cuid()) 130 | userId String 131 | providerType String @map("provider_type") 132 | providerId String @map("provider_id") 133 | providerAccountId String 134 | refresh_token String? 135 | access_token String? 136 | accessTokenExpires DateTime? @map("access_token_expires") 137 | createdAt DateTime @default(now()) @map("created_at") 138 | updatedAt DateTime @updatedAt @map("updated_at") 139 | user User @relation(fields: [userId], references: [id]) 140 | 141 | @@unique([providerId, providerAccountId]) 142 | } 143 | 144 | model User { 145 | id String @id 146 | accounts Account[] 147 | } 148 | " 149 | `; 150 | 151 | exports[`it can enforce a specified case convention on all fields of all tables (camelCase): camelCase 1`] = ` 152 | "datasource db { 153 | provider = "sqlite" 154 | url = "file:database.db" 155 | } 156 | 157 | // generator 158 | generator client { 159 | provider = "prisma-client-js" 160 | } 161 | 162 | model DemoIt { 163 | id Int @id 164 | dataField String 165 | } 166 | " 167 | `; 168 | 169 | exports[`it can enforce a specified case convention on all fields of all tables (camelCase): camelCase 2`] = ` 170 | "datasource db { 171 | provider = "postgresql" 172 | url = env("DATABASE_URL") 173 | } 174 | 175 | generator client { 176 | provider = "prisma-client-js" 177 | } 178 | 179 | model Projects { 180 | id Int @id @default(autoincrement()) 181 | name String? @db.VarChar 182 | jiraIssues JiraIssues[] 183 | 184 | @@map("projects") 185 | } 186 | 187 | model JiraIssues { 188 | id Int @id @default(autoincrement()) 189 | jiraIntegrationId Int? 190 | projectId Int 191 | projects Projects? @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "jira_issues_projects_fkey") 192 | 193 | @@map("jira_issues") 194 | } 195 | 196 | model FieldKey { 197 | key String 198 | type String // str, num, bool 199 | formId String @db.Uuid 200 | form Form @relation(references: [id], fields: [formId], onDelete: Cascade) 201 | 202 | @@id([key, formId]) 203 | @@map("field_key") 204 | } 205 | " 206 | `; 207 | 208 | exports[`it can enforce a specified case convention on all fields of all tables (camelCase): camelCase 3`] = ` 209 | "datasource db { 210 | provider = "sqlite" 211 | url = env("DATABASE_URL") 212 | } 213 | 214 | generator client { 215 | provider = "prisma-client-js" 216 | } 217 | 218 | model Business { 219 | id String @id(map: "BusinessId") @default(dbgenerated("gen_random_uuid()")) @db.Uuid 220 | languageCode String @db.Char(2) 221 | currencyCode String @db.Char(3) 222 | createdAt DateTime @default(now()) @db.Timestamptz(0) 223 | name String @db.VarChar(64) 224 | language String[] @default([]) @db.Char(2) 225 | phone String @db.VarChar(15) 226 | address String? @db.VarChar(250) 227 | billingName String? @db.VarChar(64) 228 | billingAddress String? @db.VarChar(250) 229 | taxOffice String? @db.VarChar(64) 230 | taxId String? @db.VarChar(64) 231 | defaultTaxRate Decimal? @db.Decimal(8, 6) 232 | isActive Boolean @default(true) 233 | batchOperation BatchOperation[] 234 | currency Currency @relation(fields: [currencyCode], references: [code], onUpdate: Restrict, map: "BusinessCurrency") 235 | language Language @relation(fields: [languageCode], references: [code], onUpdate: Restrict, map: "BusinessLanguage") 236 | ingredientCategory IngredientCategory[] 237 | itemCategory ItemCategory[] 238 | optionCategory OptionCategory[] 239 | profile Profile[] 240 | recipe Recipe[] 241 | tab Tab[] 242 | targetGroup TargetGroup[] 243 | 244 | @@schema("public") 245 | } 246 | " 247 | `; 248 | 249 | exports[`it can enforce a specified case convention on all fields of all tables (pascalCase): pascalCase 1`] = ` 250 | "datasource db { 251 | provider = "sqlite" 252 | url = "file:database.db" 253 | } 254 | 255 | // generator 256 | generator client { 257 | provider = "prisma-client-js" 258 | } 259 | 260 | model DemoIt { 261 | id Int @id @map("Id") 262 | dataField String @map("DataField") 263 | } 264 | " 265 | `; 266 | 267 | exports[`it can enforce a specified case convention on all fields of all tables (pascalCase): pascalCase 2`] = ` 268 | "datasource db { 269 | provider = "postgresql" 270 | url = env("DATABASE_URL") 271 | } 272 | 273 | generator client { 274 | provider = "prisma-client-js" 275 | } 276 | 277 | model Projects { 278 | id Int @id @default(autoincrement()) @map("Id") 279 | name String? @map("Name") @db.VarChar 280 | jiraIssues JiraIssues[] 281 | 282 | @@map("projects") 283 | } 284 | 285 | model JiraIssues { 286 | id Int @id @default(autoincrement()) @map("Id") 287 | jiraIntegrationId Int? @map("JiraIntegrationId") 288 | projectId Int @map("ProjectId") 289 | projects Projects? @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "jira_issues_projects_fkey") 290 | 291 | @@map("jira_issues") 292 | } 293 | 294 | model FieldKey { 295 | key String @map("Key") 296 | type String @map("Type") // str, num, bool 297 | formId String @map("FormId") @db.Uuid 298 | form Form @relation(references: [id], fields: [formId], onDelete: Cascade) 299 | 300 | @@id([key, formId]) 301 | @@map("field_key") 302 | } 303 | " 304 | `; 305 | 306 | exports[`it can enforce a specified case convention on all fields of all tables (pascalCase): pascalCase 3`] = ` 307 | "datasource db { 308 | provider = "sqlite" 309 | url = env("DATABASE_URL") 310 | } 311 | 312 | generator client { 313 | provider = "prisma-client-js" 314 | } 315 | 316 | model Business { 317 | id String @id(map: "BusinessId") @default(dbgenerated("gen_random_uuid()")) @map("Id") @db.Uuid 318 | languageCode String @map("LanguageCode") @db.Char(2) 319 | currencyCode String @map("CurrencyCode") @db.Char(3) 320 | createdAt DateTime @default(now()) @map("CreatedAt") @db.Timestamptz(0) 321 | name String @map("Name") @db.VarChar(64) 322 | language String[] @default([]) @map("Language") @db.Char(2) 323 | phone String @map("Phone") @db.VarChar(15) 324 | address String? @map("Address") @db.VarChar(250) 325 | billingName String? @map("BillingName") @db.VarChar(64) 326 | billingAddress String? @map("BillingAddress") @db.VarChar(250) 327 | taxOffice String? @map("TaxOffice") @db.VarChar(64) 328 | taxId String? @map("TaxId") @db.VarChar(64) 329 | defaultTaxRate Decimal? @map("DefaultTaxRate") @db.Decimal(8, 6) 330 | isActive Boolean @default(true) @map("IsActive") 331 | batchOperation BatchOperation[] 332 | currency Currency @relation(fields: [currencyCode], references: [code], onUpdate: Restrict, map: "BusinessCurrency") 333 | language Language @relation(fields: [languageCode], references: [code], onUpdate: Restrict, map: "BusinessLanguage") 334 | ingredientCategory IngredientCategory[] 335 | itemCategory ItemCategory[] 336 | optionCategory OptionCategory[] 337 | profile Profile[] 338 | recipe Recipe[] 339 | tab Tab[] 340 | targetGroup TargetGroup[] 341 | 342 | @@schema("public") 343 | } 344 | " 345 | `; 346 | 347 | exports[`it can enforce a specified case convention on all fields of all tables (snakeCase): snakeCase 1`] = ` 348 | "datasource db { 349 | provider = "sqlite" 350 | url = "file:database.db" 351 | } 352 | 353 | // generator 354 | generator client { 355 | provider = "prisma-client-js" 356 | } 357 | 358 | model DemoIt { 359 | id Int @id 360 | dataField String @map("data_field") 361 | } 362 | " 363 | `; 364 | 365 | exports[`it can enforce a specified case convention on all fields of all tables (snakeCase): snakeCase 2`] = ` 366 | "datasource db { 367 | provider = "postgresql" 368 | url = env("DATABASE_URL") 369 | } 370 | 371 | generator client { 372 | provider = "prisma-client-js" 373 | } 374 | 375 | model Projects { 376 | id Int @id @default(autoincrement()) 377 | name String? @db.VarChar 378 | jiraIssues JiraIssues[] 379 | 380 | @@map("projects") 381 | } 382 | 383 | model JiraIssues { 384 | id Int @id @default(autoincrement()) 385 | jiraIntegrationId Int? @map("jira_integration_id") 386 | projectId Int @map("project_id") 387 | projects Projects? @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "jira_issues_projects_fkey") 388 | 389 | @@map("jira_issues") 390 | } 391 | 392 | model FieldKey { 393 | key String 394 | type String // str, num, bool 395 | formId String @map("form_id") @db.Uuid 396 | form Form @relation(references: [id], fields: [formId], onDelete: Cascade) 397 | 398 | @@id([key, formId]) 399 | @@map("field_key") 400 | } 401 | " 402 | `; 403 | 404 | exports[`it can enforce a specified case convention on all fields of all tables (snakeCase): snakeCase 3`] = ` 405 | "datasource db { 406 | provider = "sqlite" 407 | url = env("DATABASE_URL") 408 | } 409 | 410 | generator client { 411 | provider = "prisma-client-js" 412 | } 413 | 414 | model Business { 415 | id String @id(map: "BusinessId") @default(dbgenerated("gen_random_uuid()")) @db.Uuid 416 | languageCode String @map("language_code") @db.Char(2) 417 | currencyCode String @map("currency_code") @db.Char(3) 418 | createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(0) 419 | name String @db.VarChar(64) 420 | language String[] @default([]) @db.Char(2) 421 | phone String @db.VarChar(15) 422 | address String? @db.VarChar(250) 423 | billingName String? @map("billing_name") @db.VarChar(64) 424 | billingAddress String? @map("billing_address") @db.VarChar(250) 425 | taxOffice String? @map("tax_office") @db.VarChar(64) 426 | taxId String? @map("tax_id") @db.VarChar(64) 427 | defaultTaxRate Decimal? @map("default_tax_rate") @db.Decimal(8, 6) 428 | isActive Boolean @default(true) @map("is_active") 429 | batchOperation BatchOperation[] 430 | currency Currency @relation(fields: [currencyCode], references: [code], onUpdate: Restrict, map: "BusinessCurrency") 431 | language Language @relation(fields: [languageCode], references: [code], onUpdate: Restrict, map: "BusinessLanguage") 432 | ingredientCategory IngredientCategory[] 433 | itemCategory ItemCategory[] 434 | optionCategory OptionCategory[] 435 | profile Profile[] 436 | recipe Recipe[] 437 | tab Tab[] 438 | targetGroup TargetGroup[] 439 | 440 | @@schema("public") 441 | } 442 | " 443 | `; 444 | 445 | exports[`it can enforce a specified case convention on all table names (camelCase): camelCase 1`] = ` 446 | "datasource db { 447 | provider = "sqlite" 448 | url = "file:database.db" 449 | } 450 | 451 | // generator 452 | generator client { 453 | provider = "prisma-client-js" 454 | } 455 | 456 | model DemoIt { 457 | id Int @id 458 | dataField String @map("data_field") 459 | 460 | @@map("demoIt") 461 | } 462 | " 463 | `; 464 | 465 | exports[`it can enforce a specified case convention on all table names (camelCase): camelCase 2`] = ` 466 | "datasource db { 467 | provider = "postgresql" 468 | url = env("DATABASE_URL") 469 | } 470 | 471 | generator client { 472 | provider = "prisma-client-js" 473 | } 474 | 475 | model Projects { 476 | id Int @id @default(autoincrement()) 477 | name String? @db.VarChar 478 | jiraIssues JiraIssues[] 479 | 480 | @@map("projects") 481 | } 482 | 483 | model JiraIssues { 484 | id Int @id @default(autoincrement()) 485 | jiraIntegrationId Int? @map("jira_integration_id") 486 | projectId Int @map("project_id") 487 | projects Projects? @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "jira_issues_projects_fkey") 488 | 489 | @@map("jiraIssues") 490 | } 491 | 492 | model FieldKey { 493 | key String 494 | type String // str, num, bool 495 | formId String @map("form_id") @db.Uuid 496 | form Form @relation(references: [id], fields: [formId], onDelete: Cascade) 497 | 498 | @@id([key, formId]) 499 | @@map("fieldKey") 500 | } 501 | " 502 | `; 503 | 504 | exports[`it can enforce a specified case convention on all table names (camelCase): camelCase 3`] = ` 505 | "datasource db { 506 | provider = "sqlite" 507 | url = env("DATABASE_URL") 508 | } 509 | 510 | generator client { 511 | provider = "prisma-client-js" 512 | } 513 | 514 | model Business { 515 | id String @id(map: "BusinessId") @default(dbgenerated("gen_random_uuid()")) @db.Uuid 516 | languageCode String @db.Char(2) 517 | currencyCode String @db.Char(3) 518 | createdAt DateTime @default(now()) @db.Timestamptz(0) 519 | name String @db.VarChar(64) 520 | language String[] @default([]) @db.Char(2) 521 | phone String @db.VarChar(15) 522 | address String? @db.VarChar(250) 523 | billingName String? @db.VarChar(64) 524 | billingAddress String? @db.VarChar(250) 525 | taxOffice String? @db.VarChar(64) 526 | taxId String? @db.VarChar(64) 527 | defaultTaxRate Decimal? @db.Decimal(8, 6) 528 | isActive Boolean @default(true) 529 | batchOperation BatchOperation[] 530 | currency Currency @relation(fields: [currencyCode], references: [code], onUpdate: Restrict, map: "BusinessCurrency") 531 | language Language @relation(fields: [languageCode], references: [code], onUpdate: Restrict, map: "BusinessLanguage") 532 | ingredientCategory IngredientCategory[] 533 | itemCategory ItemCategory[] 534 | optionCategory OptionCategory[] 535 | profile Profile[] 536 | recipe Recipe[] 537 | tab Tab[] 538 | targetGroup TargetGroup[] 539 | 540 | @@map("business") 541 | @@schema("public") 542 | } 543 | " 544 | `; 545 | 546 | exports[`it can enforce a specified case convention on all table names (pascalCase): pascalCase 1`] = ` 547 | "datasource db { 548 | provider = "sqlite" 549 | url = "file:database.db" 550 | } 551 | 552 | // generator 553 | generator client { 554 | provider = "prisma-client-js" 555 | } 556 | 557 | model DemoIt { 558 | id Int @id 559 | dataField String @map("data_field") 560 | } 561 | " 562 | `; 563 | 564 | exports[`it can enforce a specified case convention on all table names (pascalCase): pascalCase 2`] = ` 565 | "datasource db { 566 | provider = "postgresql" 567 | url = env("DATABASE_URL") 568 | } 569 | 570 | generator client { 571 | provider = "prisma-client-js" 572 | } 573 | 574 | model Projects { 575 | id Int @id @default(autoincrement()) 576 | name String? @db.VarChar 577 | jiraIssues JiraIssues[] 578 | } 579 | 580 | model JiraIssues { 581 | id Int @id @default(autoincrement()) 582 | jiraIntegrationId Int? @map("jira_integration_id") 583 | projectId Int @map("project_id") 584 | projects Projects? @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "jira_issues_projects_fkey") 585 | } 586 | 587 | model FieldKey { 588 | key String 589 | type String // str, num, bool 590 | formId String @map("form_id") @db.Uuid 591 | form Form @relation(references: [id], fields: [formId], onDelete: Cascade) 592 | 593 | @@id([key, formId]) 594 | } 595 | " 596 | `; 597 | 598 | exports[`it can enforce a specified case convention on all table names (pascalCase): pascalCase 3`] = ` 599 | "datasource db { 600 | provider = "sqlite" 601 | url = env("DATABASE_URL") 602 | } 603 | 604 | generator client { 605 | provider = "prisma-client-js" 606 | } 607 | 608 | model Business { 609 | id String @id(map: "BusinessId") @default(dbgenerated("gen_random_uuid()")) @db.Uuid 610 | languageCode String @db.Char(2) 611 | currencyCode String @db.Char(3) 612 | createdAt DateTime @default(now()) @db.Timestamptz(0) 613 | name String @db.VarChar(64) 614 | language String[] @default([]) @db.Char(2) 615 | phone String @db.VarChar(15) 616 | address String? @db.VarChar(250) 617 | billingName String? @db.VarChar(64) 618 | billingAddress String? @db.VarChar(250) 619 | taxOffice String? @db.VarChar(64) 620 | taxId String? @db.VarChar(64) 621 | defaultTaxRate Decimal? @db.Decimal(8, 6) 622 | isActive Boolean @default(true) 623 | batchOperation BatchOperation[] 624 | currency Currency @relation(fields: [currencyCode], references: [code], onUpdate: Restrict, map: "BusinessCurrency") 625 | language Language @relation(fields: [languageCode], references: [code], onUpdate: Restrict, map: "BusinessLanguage") 626 | ingredientCategory IngredientCategory[] 627 | itemCategory ItemCategory[] 628 | optionCategory OptionCategory[] 629 | profile Profile[] 630 | recipe Recipe[] 631 | tab Tab[] 632 | targetGroup TargetGroup[] 633 | 634 | @@schema("public") 635 | } 636 | " 637 | `; 638 | 639 | exports[`it can enforce a specified case convention on all table names (snakeCase): snakeCase 1`] = ` 640 | "datasource db { 641 | provider = "sqlite" 642 | url = "file:database.db" 643 | } 644 | 645 | // generator 646 | generator client { 647 | provider = "prisma-client-js" 648 | } 649 | 650 | model DemoIt { 651 | id Int @id 652 | dataField String @map("data_field") 653 | 654 | @@map("demo_it") 655 | } 656 | " 657 | `; 658 | 659 | exports[`it can enforce a specified case convention on all table names (snakeCase): snakeCase 2`] = ` 660 | "datasource db { 661 | provider = "postgresql" 662 | url = env("DATABASE_URL") 663 | } 664 | 665 | generator client { 666 | provider = "prisma-client-js" 667 | } 668 | 669 | model Projects { 670 | id Int @id @default(autoincrement()) 671 | name String? @db.VarChar 672 | jiraIssues JiraIssues[] 673 | 674 | @@map("projects") 675 | } 676 | 677 | model JiraIssues { 678 | id Int @id @default(autoincrement()) 679 | jiraIntegrationId Int? @map("jira_integration_id") 680 | projectId Int @map("project_id") 681 | projects Projects? @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "jira_issues_projects_fkey") 682 | 683 | @@map("jira_issues") 684 | } 685 | 686 | model FieldKey { 687 | key String 688 | type String // str, num, bool 689 | formId String @map("form_id") @db.Uuid 690 | form Form @relation(references: [id], fields: [formId], onDelete: Cascade) 691 | 692 | @@id([key, formId]) 693 | @@map("field_key") 694 | } 695 | " 696 | `; 697 | 698 | exports[`it can enforce a specified case convention on all table names (snakeCase): snakeCase 3`] = ` 699 | "datasource db { 700 | provider = "sqlite" 701 | url = env("DATABASE_URL") 702 | } 703 | 704 | generator client { 705 | provider = "prisma-client-js" 706 | } 707 | 708 | model Business { 709 | id String @id(map: "BusinessId") @default(dbgenerated("gen_random_uuid()")) @db.Uuid 710 | languageCode String @db.Char(2) 711 | currencyCode String @db.Char(3) 712 | createdAt DateTime @default(now()) @db.Timestamptz(0) 713 | name String @db.VarChar(64) 714 | language String[] @default([]) @db.Char(2) 715 | phone String @db.VarChar(15) 716 | address String? @db.VarChar(250) 717 | billingName String? @db.VarChar(64) 718 | billingAddress String? @db.VarChar(250) 719 | taxOffice String? @db.VarChar(64) 720 | taxId String? @db.VarChar(64) 721 | defaultTaxRate Decimal? @db.Decimal(8, 6) 722 | isActive Boolean @default(true) 723 | batchOperation BatchOperation[] 724 | currency Currency @relation(fields: [currencyCode], references: [code], onUpdate: Restrict, map: "BusinessCurrency") 725 | language Language @relation(fields: [languageCode], references: [code], onUpdate: Restrict, map: "BusinessLanguage") 726 | ingredientCategory IngredientCategory[] 727 | itemCategory ItemCategory[] 728 | optionCategory OptionCategory[] 729 | profile Profile[] 730 | recipe Recipe[] 731 | tab Tab[] 732 | targetGroup TargetGroup[] 733 | 734 | @@map("business") 735 | @@schema("public") 736 | } 737 | " 738 | `; 739 | 740 | exports[`it can enforce a specified case convention on views 1`] = ` 741 | "generator client { 742 | provider = "prisma-client-js" 743 | output = "../node_modules/@generated/read" 744 | previewFeatures = ["views"] 745 | } 746 | 747 | generator typegraphql { 748 | provider = "typegraphql-prisma" 749 | } 750 | 751 | datasource db { 752 | provider = "postgres" 753 | url = env("DB_URL") 754 | } 755 | 756 | model GooseDbVersion { 757 | id Int @id @default(autoincrement()) 758 | versionId BigInt @map("version_id") 759 | isApplied Boolean @map("is_applied") 760 | tstamp DateTime? @default(now()) @db.Timestamp(6) 761 | circleValue Unsupported("circle")? @map("circle_value") 762 | 763 | @@map("goose_db_version") 764 | } 765 | 766 | view Accounts { 767 | id String @unique @db.Uuid 768 | createdAt DateTime? @map("created_at") @db.Timestamp(6) 769 | name String? 770 | updatedAt DateTime? @map("updated_at") @db.Timestamp(6) 771 | ownedBy String? @map("owned_by") @db.Uuid 772 | users User[] 773 | 774 | @@map("accounts") 775 | } 776 | 777 | model User { 778 | id String @id 779 | name String 780 | accounts Accounts? @relation(fields: [accountsId], references: [id]) 781 | accountsId String? @map("accounts_id") @db.Uuid 782 | 783 | @@map("user") 784 | } 785 | " 786 | `; 787 | 788 | exports[`it can map ...horrendous indexes that make your head hurt for these people 1`] = ` 789 | "datasource db { 790 | provider = "mysql" 791 | url = "file:database.db" 792 | } 793 | 794 | // generator 795 | generator client { 796 | provider = "prisma-client-js" 797 | } 798 | 799 | model Post { 800 | @@map("post") 801 | id Int @id @default(autoincrement()) 802 | titlePain String @map("title_pain") 803 | contentHash String? @map("content_hash") 804 | 805 | @@index(fields: [titlePain, contentHash(length: 12)], name: "main_index") 806 | } 807 | 808 | model Address { 809 | @@map("address") 810 | id Int @id 811 | street String 812 | number Int 813 | user User[] 814 | } 815 | 816 | model User { 817 | @@map("user") 818 | id Int @id 819 | email String 820 | address Address @relation(fields: [addressId], references: [id]) 821 | addressId Int @map("address_id") 822 | 823 | @@index([addressId], map: "an_index_name") 824 | } 825 | " 826 | `; 827 | 828 | exports[`it can map enum column to enum definition 1`] = ` 829 | "datasource db { 830 | provider = "mysql" 831 | url = env("DATABASE_URL") 832 | } 833 | 834 | generator client { 835 | provider = "prisma-client-js" 836 | } 837 | 838 | model Posts { 839 | id Int @id @default(autoincrement()) 840 | content String? @db.VarChar(245) 841 | post_type PostType @map("postType") 842 | 843 | @@map("posts") 844 | } 845 | 846 | enum PostType { 847 | Note 848 | Question 849 | 850 | @@map("post_type") 851 | } 852 | 853 | enum SnaggleFlark { 854 | foo 855 | buz 856 | bazz 857 | 858 | @@map("snaggle_flark") 859 | } 860 | 861 | enum AppleColors { 862 | green 863 | red 864 | } 865 | " 866 | `; 867 | 868 | exports[`it can rename enum in the database 1`] = ` 869 | "enum UserLocale { 870 | fr_FR 871 | en_EN 872 | 873 | @@map("user_locale") 874 | } 875 | 876 | model User { 877 | id Int @id @default(autoincrement()) 878 | email String @unique 879 | firstName String @default("") @map("first_name") 880 | lastName String @default("") @map("last_name") 881 | locale UserLocale @default(fr_FR) 882 | 883 | @@map("user") 884 | } 885 | " 886 | `; 887 | 888 | exports[`the readme demo should work 1`] = ` 889 | "model HouseRating { 890 | @@map("house_rating") 891 | Id Int @map("id") @id @default(autoincrement()) 892 | HouseId String @map("house_id") 893 | House House @relation(fields: [HouseId], references: [Id]) 894 | } 895 | 896 | model House { 897 | @@map("house") 898 | Id String @map("id") @id @default(uuid()) // comments 899 | HouseRating HouseRating[] // do comments get 900 | } 901 | " 902 | `; 903 | -------------------------------------------------------------------------------- /test/convention-transformer.test.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | 3 | import { join } from 'path'; 4 | 5 | import { camelCase, pascalCase, snakeCase } from 'change-case'; 6 | import { CaseChange, ConventionTransformer } from '../src/convention-transformer'; 7 | import { formatSchema } from '@prisma/internals'; 8 | 9 | import { asPluralized, asSingularized } from '../src/caseConventions'; 10 | import { ConventionStore, defaultConventions } from '../src/convention-store'; 11 | 12 | const PRISMA_SUFFIX = '.schema.prisma'; 13 | const FIXTURES_DIR = join(process.cwd(), 'test', '__fixtures__'); 14 | function getFixture(relative_path: string) { 15 | return readFileSync(join(FIXTURES_DIR, relative_path + PRISMA_SUFFIX), 'utf-8'); 16 | } 17 | 18 | function getTestFile(relative_path: string) { 19 | return readFileSync(join(FIXTURES_DIR, relative_path), 'utf-8'); 20 | } 21 | 22 | test('the readme demo should work', () => { 23 | const file_contents = getFixture('readme-demo'); 24 | const store = ConventionStore.fromConventions({ 25 | ...defaultConventions(), 26 | fieldCaseConvention: pascalCase, 27 | }); 28 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 29 | expect(err).toBeFalsy(); 30 | expect(result).toMatchSnapshot(); 31 | }); 32 | 33 | test('it can map model columns with under_scores to camelCase', () => { 34 | const file_contents = getFixture('model-columns-with-underscores'); 35 | 36 | const store = ConventionStore.fromConventions({ 37 | ...defaultConventions(), 38 | tableCaseConvention: pascalCase, 39 | fieldCaseConvention: camelCase, 40 | pluralize: false 41 | }); 42 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 43 | expect(err).toBeFalsy(); 44 | expect(result?.includes('articleId Int @map("article_id")')).toBeTruthy(); 45 | }); 46 | 47 | test('it can map relations with cascading deletion rules & foreign_key names', () => { 48 | const file_contents = getFixture('cascading-deletion-and-fk') 49 | const store = ConventionStore.fromConventions({ 50 | ...defaultConventions(), 51 | tableCaseConvention: pascalCase, 52 | fieldCaseConvention: camelCase, 53 | pluralize: false 54 | }); 55 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 56 | expect(err).toBeFalsy(); 57 | expect(result?.includes('@relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "jira_issues_projects_fkey")')).toBeTruthy(); 58 | expect(result?.includes('@relation(references: [id], fields: [formId], onDelete: Cascade)')).toBeTruthy(); 59 | }); 60 | 61 | test('it can map enum column to enum definition', async () => { 62 | const store = ConventionStore.fromConventions({ 63 | ...defaultConventions(), 64 | tableCaseConvention: pascalCase, 65 | fieldCaseConvention: snakeCase, 66 | enumCaseConvention: pascalCase, 67 | pluralize: false, 68 | }); 69 | const file_contents = getFixture('enum'); 70 | const [schema, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 71 | expect(err).toBeFalsy(); 72 | const new_schema = await formatSchema({ schema: schema! }); 73 | expect(new_schema).toMatchSnapshot(); 74 | }); 75 | 76 | test('it can optionally pluralize fields', () => { 77 | const file_contents = getFixture('pluralize-fields'); 78 | const store = ConventionStore.fromConventions({ 79 | ...defaultConventions(), 80 | tableCaseConvention: pascalCase, 81 | fieldCaseConvention: camelCase, 82 | pluralize: true 83 | }); 84 | let [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 85 | expect(err).toBeFalsy(); 86 | expect(result?.includes(`ingredientCategories IngredientCategory[]`)).toBeTruthy(); 87 | expect(result).toMatch(/languages\s+String\[\].+(@map\("language"\))/); 88 | 89 | // prove is optional 90 | store.pluralize = false; 91 | [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 92 | expect(err).toBeFalsy(); 93 | expect(result).toMatch(/ingredientCategory\s+IngredientCategory\[\]/); 94 | expect(result).toMatch(/language\s+String\[\]/); 95 | }); 96 | 97 | test('it can account for comments on model lines', () => { 98 | const file_contents = getFixture('comments-on-model-lines'); 99 | 100 | const store = ConventionStore.fromConventions({ 101 | ...defaultConventions(), 102 | tableCaseConvention: pascalCase, 103 | fieldCaseConvention: camelCase, 104 | pluralize: false, 105 | }); 106 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 107 | expect(err).toBeFalsy(); 108 | expect(result).toMatch(/fieldWithComments\s+String\?\s+@map\("field_with_comments"\)\s+\/\/ This should not break our ability to insert map annotations/); 109 | }); 110 | 111 | const supported_case_conventions: { caseConvention: CaseChange }[] = [ 112 | { caseConvention: snakeCase }, { caseConvention: camelCase }, { caseConvention: pascalCase }]; 113 | /** 114 | * !!Warning!! Jest snapshots are _almost_ an anti-pattern. This is because if 115 | * you rename the test case, and introduce a bug, the bug is now valid to Jest. 116 | * This means you _must_ participate in orthodox red-green-refactor. 117 | * 118 | * If you rename this test, don't do it while the build is broken. 119 | */ 120 | test.each(supported_case_conventions)('it can enforce a specified case convention on all fields of all tables ($caseConvention.name)', async ({ caseConvention }) => { 121 | const store = ConventionStore.fromConventions({ 122 | ...defaultConventions(), 123 | tableCaseConvention: pascalCase, 124 | fieldCaseConvention: camelCase, 125 | mapFieldCaseConvention: caseConvention, 126 | pluralize: false 127 | }); 128 | 129 | let file_contents = getFixture('idempotency'); 130 | let [schema, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 131 | let new_schema = await formatSchema({ schema: schema! }); 132 | expect(err).toBeFalsy(); 133 | expect(new_schema).toMatchSnapshot(caseConvention.name); 134 | 135 | file_contents = getFixture('cascading-deletion-and-fk'); 136 | [schema, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 137 | new_schema = await formatSchema({ schema: schema! }); 138 | expect(err).toBeFalsy(); 139 | expect(new_schema).toMatchSnapshot(caseConvention.name); 140 | 141 | file_contents = getFixture('pluralize-fields'); 142 | [schema, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 143 | new_schema = await formatSchema({ schema: schema! }); 144 | expect(err).toBeFalsy(); 145 | expect(new_schema).toMatchSnapshot(caseConvention.name); 146 | }); 147 | 148 | /** 149 | * !!Warning!! Jest snapshots are _almost_ an anti-pattern. This is because if 150 | * you rename the test case, and introduce a bug, the bug is now valid to Jest. 151 | * This means you _must_ participate in orthodox red-green-refactor. 152 | * 153 | * If you rename this test, don't do it while the build is broken. 154 | */ 155 | test.each(supported_case_conventions)('it can enforce a specified case convention on all table names ($caseConvention.name)', async ({ caseConvention }) => { 156 | const store = ConventionStore.fromConventions({ 157 | ...defaultConventions(), 158 | tableCaseConvention: pascalCase, 159 | fieldCaseConvention: camelCase, 160 | mapTableCaseConvention: caseConvention, 161 | pluralize: false 162 | }); 163 | 164 | let file_contents = getFixture('idempotency'); 165 | let [schema, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 166 | let new_schema = await formatSchema({ schema: schema! }); 167 | expect(err).toBeFalsy(); 168 | expect(new_schema).toMatchSnapshot(caseConvention.name); 169 | 170 | file_contents = getFixture('cascading-deletion-and-fk'); 171 | [schema, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 172 | new_schema = await formatSchema({ schema: schema! }); 173 | expect(err).toBeFalsy(); 174 | expect(new_schema).toMatchSnapshot(caseConvention.name); 175 | 176 | file_contents = getFixture('pluralize-fields'); 177 | [schema, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 178 | new_schema = await formatSchema({ schema: schema! }); 179 | expect(err).toBeFalsy(); 180 | expect(new_schema).toMatchSnapshot(caseConvention.name); 181 | }); 182 | 183 | test('it can enforce a specified case convention on views', async () => { 184 | const store = ConventionStore.fromConventions({ 185 | ...defaultConventions(), 186 | tableCaseConvention: pascalCase, 187 | fieldCaseConvention: camelCase, 188 | pluralize: true, 189 | }); 190 | const file_contents = getFixture('views'); 191 | let [schema, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 192 | expect(err).toBeFalsy(); 193 | let new_schema = await formatSchema({ schema: schema! }); 194 | expect(new_schema).toMatchSnapshot(); 195 | }); 196 | 197 | test('it can map columns with `view` in the name', () => { 198 | const file_contents = getFixture('columns-with-view-in-name'); 199 | 200 | const store = ConventionStore.fromConventions({ 201 | ...defaultConventions(), 202 | tableCaseConvention: pascalCase, 203 | fieldCaseConvention: camelCase, 204 | pluralize: false 205 | }); 206 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 207 | expect(err).toBeFalsy(); 208 | expect(result?.includes('viewCount Int @map("view_count")')).toBeTruthy(); 209 | }); 210 | 211 | test('it can map tables while separating pluralization', () => { 212 | const file_contents = getFixture('tables-with-pluralized-db-targets'); 213 | 214 | const store = ConventionStore.fromConventions({ 215 | ...defaultConventions(), 216 | tableCaseConvention: pascalCase, 217 | fieldCaseConvention: camelCase, 218 | mapTableCaseConvention: asPluralized(snakeCase), 219 | pluralize: false, 220 | }); 221 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 222 | expect(err).toBeFalsy(); 223 | expect(result?.includes('@@map("users")')).toBeTruthy(); 224 | expect(result?.includes('@@map("groups")')).toBeTruthy(); 225 | }); 226 | 227 | test('it can map tables while separating pluralization', () => { 228 | const file_contents = getFixture('tables-with-pluralized-db-targets'); 229 | 230 | const store = ConventionStore.fromConventions({ 231 | ...defaultConventions(), 232 | tableCaseConvention: pascalCase, 233 | fieldCaseConvention: camelCase, 234 | mapTableCaseConvention: asSingularized(snakeCase), 235 | pluralize: false, 236 | }); 237 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 238 | expect(err).toBeFalsy(); 239 | expect(result?.includes('model Sisters')).toBeTruthy(); 240 | expect(result?.includes('@@map("sister")')).toBeTruthy(); 241 | expect(result?.includes('model Brothers')).toBeTruthy(); 242 | expect(result?.includes('@@map("brother")')).toBeTruthy(); 243 | }); 244 | 245 | test('it can map ...horrendous indexes that make your head hurt for these people', () => { 246 | const file_contents = getFixture('complex-index'); 247 | 248 | const store = ConventionStore.fromConventions({ 249 | ...defaultConventions(), 250 | tableCaseConvention: pascalCase, 251 | fieldCaseConvention: camelCase, 252 | mapTableCaseConvention: asSingularized(snakeCase), 253 | pluralize: false, 254 | }); 255 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 256 | expect(err).toBeFalsy(); 257 | expect(result).toMatchSnapshot(); 258 | }); 259 | 260 | test('it can rename enum in the database', async () => { 261 | const file_contents = getFixture('enum-tables-map'); 262 | 263 | const store = ConventionStore.fromConventions({ 264 | ...defaultConventions(), 265 | tableCaseConvention: pascalCase, 266 | fieldCaseConvention: camelCase, 267 | mapTableCaseConvention: snakeCase, 268 | pluralize: false, 269 | }); 270 | const [schema, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 271 | expect(err).toBeFalsy(); 272 | const result = await formatSchema({ schema: schema! }); 273 | expect(result).toMatchSnapshot(); 274 | }); 275 | 276 | describe('must properly bring enum name to', () => { 277 | test('camelCase', () => { 278 | const file_contents = getFixture('enum'); 279 | 280 | const store = ConventionStore.fromConventions({ 281 | ...defaultConventions(), 282 | tableCaseConvention: pascalCase, 283 | fieldCaseConvention: camelCase, 284 | enumCaseConvention: camelCase, 285 | }); 286 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 287 | expect(err).toBeFalsy(); 288 | expect(result?.includes('enum postType')).toBeTruthy(); 289 | }) 290 | 291 | test('PascalCase', () => { 292 | const file_contents = getFixture('enum'); 293 | 294 | const store = ConventionStore.fromConventions({ 295 | ...defaultConventions(), 296 | tableCaseConvention: pascalCase, 297 | fieldCaseConvention: camelCase, 298 | enumCaseConvention: pascalCase, 299 | }); 300 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 301 | expect(err).toBeFalsy(); 302 | expect(result?.includes('enum PostType')).toBeTruthy(); 303 | }) 304 | 305 | test('snake_case', () => { 306 | const file_contents = getFixture('enum'); 307 | 308 | const store = ConventionStore.fromConventions({ 309 | ...defaultConventions(), 310 | tableCaseConvention: pascalCase, 311 | fieldCaseConvention: camelCase, 312 | enumCaseConvention: snakeCase, 313 | }); 314 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 315 | expect(err).toBeFalsy(); 316 | expect(result?.includes('enum post_type')).toBeTruthy(); 317 | }); 318 | }); 319 | 320 | describe('must properly map enum name to ', () => { 321 | test('camelCase', () => { 322 | const file_contents = getFixture('enum'); 323 | 324 | const store = ConventionStore.fromConventions({ 325 | ...defaultConventions(), 326 | tableCaseConvention: pascalCase, 327 | fieldCaseConvention: camelCase, 328 | mapEnumCaseConvention: camelCase, 329 | }); 330 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 331 | expect(err).toBeFalsy(); 332 | expect(result?.includes('@@map("postType")')).toBeTruthy(); 333 | }); 334 | 335 | test('PascalCase', () => { 336 | const file_contents = getFixture('enum'); 337 | 338 | const store = ConventionStore.fromConventions({ 339 | ...defaultConventions(), 340 | tableCaseConvention: pascalCase, 341 | fieldCaseConvention: camelCase, 342 | mapEnumCaseConvention: pascalCase, 343 | }); 344 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 345 | expect(err).toBeFalsy(); 346 | expect(result?.includes('@@map("PostType")')).toBeFalsy(); 347 | }); 348 | 349 | test('snake_case', () => { 350 | const file_contents = getFixture('enum'); 351 | 352 | const store = ConventionStore.fromConventions({ 353 | ...defaultConventions(), 354 | tableCaseConvention: pascalCase, 355 | fieldCaseConvention: camelCase, 356 | mapEnumCaseConvention: snakeCase, 357 | }); 358 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 359 | expect(err).toBeFalsy(); 360 | // We can't test “post_type” here because we just skip mappings if mapped name is equal to enum name. 361 | expect(result?.includes('@@map("apple_colors")')).toBeTruthy(); 362 | }); 363 | }); 364 | 365 | test('use table naming convention when enum naming convention is not specified', () => { 366 | const file_contents = getFixture('enum'); 367 | 368 | const store = ConventionStore.fromConventions({ 369 | ...defaultConventions(), 370 | tableCaseConvention: pascalCase, 371 | fieldCaseConvention: camelCase, 372 | }); 373 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 374 | expect(err).toBeFalsy(); 375 | expect(result?.includes('enum PostType')).toBeTruthy(); 376 | }); 377 | 378 | test('override table naming convention when enum naming convention is specified', () => { 379 | const file_contents = getFixture('enum'); 380 | 381 | const store = ConventionStore.fromConventions({ 382 | ...defaultConventions(), 383 | tableCaseConvention: pascalCase, 384 | fieldCaseConvention: camelCase, 385 | enumCaseConvention: camelCase, 386 | }); 387 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 388 | expect(err).toBeFalsy(); 389 | expect(result?.includes('enum postType')).toBeTruthy(); 390 | }); 391 | 392 | test('use table mapping convention when enum mapping convention is not specified', () => { 393 | const file_contents = getFixture('enum'); 394 | 395 | const store = ConventionStore.fromConventions({ 396 | ...defaultConventions(), 397 | tableCaseConvention: pascalCase, 398 | fieldCaseConvention: camelCase, 399 | mapTableCaseConvention: camelCase, 400 | }); 401 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 402 | expect(err).toBeFalsy(); 403 | expect(result?.includes('@@map("postType")')).toBeTruthy(); 404 | }); 405 | 406 | test('override table mapping convention when enum mapping convention is specified', () => { 407 | const file_contents = getFixture('enum'); 408 | 409 | const store = ConventionStore.fromConventions({ 410 | ...defaultConventions(), 411 | tableCaseConvention: pascalCase, 412 | fieldCaseConvention: camelCase, 413 | mapTableCaseConvention: camelCase, 414 | mapEnumCaseConvention: pascalCase, 415 | }); 416 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 417 | expect(err).toBeFalsy(); 418 | expect(result?.includes('@@map("PostType")')).toBeFalsy(); 419 | }); 420 | 421 | test('skip enum name mapping when enum name is equal to map', () => { 422 | const file_contents = getFixture('enum'); 423 | 424 | const store = ConventionStore.fromConventions({ 425 | ...defaultConventions(), 426 | tableCaseConvention: pascalCase, 427 | fieldCaseConvention: camelCase, 428 | mapEnumCaseConvention: pascalCase, 429 | }); 430 | const [result, err] = ConventionTransformer.migrateCaseConventions(file_contents, store); 431 | expect(err).toBeFalsy(); 432 | expect(result?.includes('@@map("post_type")')).toBeFalsy(); 433 | }); 434 | 435 | test('if the entity is marked disabled, or is marked with !, formatting is applied specially', async () => { 436 | const file_contents = getFixture('disable'); 437 | const cfg_file = getTestFile('disable.prisma-case-format'); 438 | const [store, store_err] = ConventionStore.fromConfStr(cfg_file); 439 | expect(store_err).toBeFalsy(); 440 | const [schema, err] = ConventionTransformer.migrateCaseConventions(file_contents, store!); 441 | expect(err).toBeFalsy(); 442 | const result = await formatSchema({ schema: schema! }); 443 | expect(result).toMatchSnapshot(); 444 | }); 445 | 446 | test('if next-auth is marked as in use, those models should be managed separately/consistently', async () => { 447 | const file_contents = getFixture('next-auth'); 448 | const [store, store_err] = ConventionStore.fromConf({ uses_next_auth: true }); 449 | expect(store_err).toBeFalsy(); 450 | const [schema, err] = ConventionTransformer.migrateCaseConventions(file_contents, store!); 451 | const result = await formatSchema({ schema: schema! }); 452 | expect(err).toBeFalsy(); 453 | expect(result).toMatchSnapshot(); 454 | }); 455 | 456 | test('issue', async () => { 457 | const file_contents = getFixture('issue'); 458 | const [store, store_err] = ConventionStore.fromConf({ 459 | uses_next_auth: true 460 | }); 461 | expect(store_err).toBeFalsy(); 462 | const [schema, err] = ConventionTransformer.migrateCaseConventions(file_contents, store!); 463 | expect(err).toBeFalsy(); 464 | const result = await formatSchema({ schema: schema! }); 465 | expect(result).toMatchSnapshot(); 466 | }); 467 | 468 | test('issue2', async () => { 469 | const file_contents = getFixture('issue2'); 470 | const [store, store_err] = ConventionStore.fromConf({ 471 | uses_next_auth: true 472 | }); 473 | expect(store_err).toBeFalsy(); 474 | const [schema, err] = ConventionTransformer.migrateCaseConventions(file_contents, store!); 475 | expect(err).toBeFalsy(); 476 | const result = await formatSchema({ schema: schema! }); 477 | expect(result).toMatchSnapshot(); 478 | }); 479 | 480 | test('disable2', async () => { 481 | const file_contents = getFixture('disable2'); 482 | const cfg_file = getTestFile('disable2.prisma-case-format'); 483 | const [store, store_err] = ConventionStore.fromConfStr(cfg_file); 484 | expect(store_err).toBeFalsy(); 485 | const [schema, err] = ConventionTransformer.migrateCaseConventions(file_contents, store!); 486 | expect(err).toBeFalsy(); 487 | const result = await formatSchema({ schema: schema! }); 488 | expect(result).toMatchSnapshot(); 489 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | // { 2 | // "compilerOptions": { 3 | // "outDir": "dist", 4 | // "strict": false, 5 | // "lib": ["esnext"], 6 | // "esModuleInterop": true, 7 | // "downlevelIteration": true 8 | // } 9 | // } 10 | { 11 | "compilerOptions": { 12 | /* Basic Options */ 13 | "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, 14 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 15 | "lib": [ 16 | "esnext" 17 | ], /* Specify library files to be included in the compilation. */ 18 | // "allowJs": true, /* Allow javascript files to be compiled. */ 19 | // "checkJs": true, /* Report errors in .js files. */ 20 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 21 | "declaration": false /* Generates corresponding '.d.ts' file. */, 22 | "sourceMap": true, /* Generates corresponding '.map' file. */ 23 | // "outFile": "./", /* Concatenate and emit output to single file. */ 24 | "outDir": "./bin" /* Redirect output structure to the directory. */, 25 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 26 | // "removeComments": true, /* Do not emit comments to output. */ 27 | // "noEmit": true, /* Do not emit outputs. */ 28 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 29 | "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 30 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 31 | /* Strict Type-Checking Options */ 32 | "strict": true /* Enable all strict type-checking options. */, 33 | "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, 34 | "strictNullChecks": true /* Enable strict null checks. */, 35 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 36 | "strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */, 37 | "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, 38 | "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, 39 | /* Additional Checks */ 40 | "noUnusedLocals": true /* Report errors on unused locals. */, 41 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 42 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 43 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 44 | /* Module Resolution Options */ 45 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 46 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 47 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 48 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 49 | // "typeRoots": [], /* List of folders to include type definitions from. */ 50 | // "types": [], /* Type declaration files to be included in compilation. */ 51 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 52 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 53 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 54 | /* Source Map Options */ 55 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 56 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 57 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 58 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | }, 63 | "exclude": [ 64 | "node_modules", 65 | "test" 66 | ] 67 | } --------------------------------------------------------------------------------