├── .commitlintrc.js ├── .czrc ├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── Taskfile ├── docs ├── after.png └── before.png ├── eslint.config.mjs ├── package.json ├── prettier.config.cjs ├── prisma ├── schema.prisma └── script.ts ├── release.config.cjs ├── src ├── index.spec.ts ├── index.ts ├── options.ts └── postgres.spec.ts └── tsconfig.json /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'subject-case': [2, 'always', ['sentence-case']], 5 | 'scope-case': [2, 'always', ['lower-case', 'upper-case']], 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /.czrc: -------------------------------------------------------------------------------- 1 | { 2 | "path": "./node_modules/cz-conventional-changelog", 3 | "disableScopeLowerCase": false, 4 | "disableSubjectLowerCase": true 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | end_of_line = lf 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | max_line_length = 80 15 | 16 | [*.{json,yml}] 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: 'Test / Build / Release' 2 | on: 3 | - push 4 | - pull_request 5 | 6 | jobs: 7 | test: 8 | name: 'Test' 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: 'Checkout repository' 12 | uses: actions/checkout@v3 13 | - name: 'Setup node' 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: 22 17 | - name: 'Install depependencies' 18 | run: npm install --force 19 | - name: 'Test' 20 | run: | 21 | npm run test 22 | 23 | release: 24 | name: 'Release' 25 | runs-on: ubuntu-latest 26 | needs: test 27 | if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/next') 28 | steps: 29 | - name: 'Checkout repository' 30 | uses: actions/checkout@v3 31 | - name: 'Setup Node' 32 | uses: actions/setup-node@v3 33 | with: 34 | node-version: 22 35 | - name: 'Install depependencies' 36 | run: | 37 | npm install --force 38 | - name: 'Build' 39 | run: | 40 | npm run build 41 | - name: 'Release' 42 | run: | 43 | npx semantic-release 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/5d896f6791c4257b74696714c66b2530b8d95a51/Node.gitignore 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | # nyc test coverage 18 | .nyc_output 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | # Bower dependency directory (https://bower.io/) 22 | bower_components 23 | # node-waf configuration 24 | .lock-wscript 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | # Dependency directories 28 | node_modules/ 29 | jspm_packages/ 30 | # Typescript v1 declaration files 31 | typings/ 32 | # Optional npm cache directory 33 | .npm 34 | # Optional eslint cache 35 | .eslintcache 36 | # Optional REPL history 37 | .node_repl_history 38 | # Output of 'npm pack' 39 | *.tgz 40 | # Yarn Integrity file 41 | .yarn-integrity 42 | # dotenv environment variables file 43 | .env 44 | # Custom 45 | dist/ 46 | ~* 47 | .idea 48 | .awcache 49 | .vscode 50 | .rts2_cache_* 51 | .stryker-tmp 52 | reports 53 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock = false 2 | loglevel = error 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [3.2.1](https://github.com/unlight/prisma-query-log/compare/v3.2.0...v3.2.1) (2024-07-01) 2 | 3 | ### Bug Fixes 4 | 5 | * Postgres insert date ([de61f33](https://github.com/unlight/prisma-query-log/commit/de61f33ba888ac8d859e2657313a52472ab6cef3)) 6 | 7 | ## [3.2.0](https://github.com/unlight/prisma-query-log/compare/v3.1.1...v3.2.0) (2022-08-11) 8 | 9 | 10 | ### Features 11 | 12 | * Added event duration ([9e61ca0](https://github.com/unlight/prisma-query-log/commit/9e61ca0ecffd866f8389cafa9c06d9cb3eddfb16)), closes [#5](https://github.com/unlight/prisma-query-log/issues/5) 13 | 14 | ### [3.1.1](https://github.com/unlight/prisma-query-log/compare/v3.1.0...v3.1.1) (2022-03-18) 15 | 16 | 17 | ### Bug Fixes 18 | 19 | * Safe parse parameters ([461dee0](https://github.com/unlight/prisma-query-log/commit/461dee0f44a700de751acfb6693ee6b903e97955)), closes [#4](https://github.com/unlight/prisma-query-log/issues/4) 20 | 21 | ## [3.1.0](https://github.com/unlight/prisma-query-log/compare/v3.0.2...v3.1.0) (2021-12-21) 22 | 23 | 24 | ### Features 25 | 26 | * Support postgres ([2ad66c2](https://github.com/unlight/prisma-query-log/commit/2ad66c250e492f2124884149987b942d95b5bc41)) 27 | 28 | ### [3.0.2](https://github.com/unlight/prisma-query-log/compare/v3.0.1...v3.0.2) (2021-10-19) 29 | 30 | 31 | ### Bug Fixes 32 | 33 | * Date parsing when timestamp has no decimals ([4655519](https://github.com/unlight/prisma-query-log/commit/46555198f83ae7175cbcec256b8dc8c647de67d9)), closes [#3](https://github.com/unlight/prisma-query-log/issues/3) 34 | 35 | ### [3.0.1](https://github.com/unlight/prisma-query-log/compare/v3.0.0...v3.0.1) (2021-04-25) 36 | 37 | 38 | ### Bug Fixes 39 | 40 | * Added github home page link to package json ([3c3b8b4](https://github.com/unlight/prisma-query-log/commit/3c3b8b4f13e630d93ec42e443426a9bb045b0810)), closes [#1](https://github.com/unlight/prisma-query-log/issues/1) [#2](https://github.com/unlight/prisma-query-log/issues/2) 41 | 42 | # [3.0.0](https://github.com/unlight/prisma-query-log/compare/v2.1.0...v3.0.0) (2021-02-12) 43 | 44 | 45 | ### Features 46 | 47 | * Made configuration flatten ([4dac682](https://github.com/unlight/prisma-query-log/commit/4dac68291d3d36febefa72c6550218b98b79b11c)) 48 | 49 | 50 | ### BREAKING CHANGES 51 | 52 | * Interface of configutation is changed, now it is flat 53 | 54 | # [2.1.0](https://github.com/unlight/prisma-query-log/compare/v2.0.3...v2.1.0) (2021-02-11) 55 | 56 | 57 | ### Features 58 | 59 | * Improved format of where in (?) statement ([cf87b9d](https://github.com/unlight/prisma-query-log/commit/cf87b9dc557e7cf68b632642db469290ec7ce17e)) 60 | 61 | ## [2.0.3](https://github.com/unlight/prisma-query-log/compare/v2.0.2...v2.0.3) (2021-01-22) 62 | 63 | 64 | ### Bug Fixes 65 | 66 | * No new line on formatted query ([44f4f2a](https://github.com/unlight/prisma-query-log/commit/44f4f2a21a713d9ac4bccb4db58deefa10d234b3)) 67 | 68 | ## [2.0.2](https://github.com/unlight/prisma-query-log/compare/v2.0.1...v2.0.2) (2021-01-14) 69 | 70 | 71 | ### Bug Fixes 72 | 73 | * Long parameters are not line breaking ([fcc7e1b](https://github.com/unlight/prisma-query-log/commit/fcc7e1b7cc7d4c7745667302954f2f8ab4bf23e3)) 74 | 75 | ## [2.0.1](https://github.com/unlight/prisma-query-log/compare/v2.0.0...v2.0.1) (2021-01-14) 76 | 77 | 78 | ### Bug Fixes 79 | 80 | * Improve sql formatter ([f51ec9a](https://github.com/unlight/prisma-query-log/commit/f51ec9a372ea2a60721c3d0b7c6c488125aabf52)) 81 | 82 | # [2.0.0](https://github.com/unlight/prisma-query-log/compare/v1.2.0...v2.0.0) (2021-01-13) 83 | 84 | 85 | ### Features 86 | 87 | * Replaced sql formatter ([4bc158c](https://github.com/unlight/prisma-query-log/commit/4bc158cd909a192d133c18d1cf628c746b625826)) 88 | 89 | 90 | ### BREAKING CHANGES 91 | 92 | * Replaced sql formatter by https://github.com/gwax/sql-formatter 93 | 94 | # [1.2.0](https://github.com/unlight/prisma-query-log/compare/v1.1.2...v1.2.0) (2021-01-13) 95 | 96 | 97 | ### Features 98 | 99 | * Option to format query ([ec26a8e](https://github.com/unlight/prisma-query-log/commit/ec26a8e7745ba542ad8628fa67f200df34cc693e)) 100 | 101 | ## [1.1.2](https://github.com/unlight/prisma-query-log/compare/v1.1.1...v1.1.2) (2020-12-25) 102 | 103 | 104 | ### Bug Fixes 105 | 106 | * Unescape single fields ([12de9c1](https://github.com/unlight/prisma-query-log/commit/12de9c10c8e254d1023a49e4a9eec749866a7199)) 107 | 108 | ## [1.1.1](https://github.com/unlight/prisma-query-log/compare/v1.1.0...v1.1.1) (2020-12-25) 109 | 110 | 111 | ### Bug Fixes 112 | 113 | * Parse date parameter ([f41d4d5](https://github.com/unlight/prisma-query-log/commit/f41d4d580474100b82506a237f2e1849c4cf2cc1)) 114 | 115 | # [1.1.0](https://github.com/unlight/prisma-query-log/compare/v1.0.0...v1.1.0) (2020-12-25) 116 | 117 | 118 | ### Features 119 | 120 | * Colorize parameters ([e290129](https://github.com/unlight/prisma-query-log/commit/e2901293b69366036aa9eb6829f9497a33b2c7c4)) 121 | 122 | # 1.0.0 (2020-12-25) 123 | 124 | 125 | ### Features 126 | 127 | * First release ([5069929](https://github.com/unlight/prisma-query-log/commit/506992996116cdd45bcd2416362edc1ef1810a99)) 128 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # prisma-query-log 2 | 3 | Log prisma query event. 4 | Currently works only for SQLite, MySQL. 5 | 6 | Features: 7 | 8 | - Substitute parameters 9 | - Remove backticks and database prefix 10 | 11 | ## Install 12 | 13 | ```sh 14 | npm install --save-dev prisma-query-log 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```typescript 20 | import { createPrismaQueryEventHandler } from 'prisma-query-log'; 21 | import { PrismaClient } from '@prisma/client'; 22 | 23 | const prisma = new PrismaClient({ 24 | log: [ 25 | { 26 | level: 'query', 27 | emit: 'event', 28 | }, 29 | ], 30 | }); 31 | 32 | const log = createPrismaQueryEventHandler(); 33 | 34 | prisma.$on('query', log); 35 | ``` 36 | 37 | ## API 38 | 39 | ```ts 40 | function createPrismaQueryEventHandler( 41 | options?: CreatePrismaQueryEventHandlerArgs, 42 | ): (event: PrismaQueryEvent) => void; 43 | 44 | const defaultOptions = { 45 | /** 46 | * Boolean of custom log function, 47 | * if true `console.log` will be used, 48 | * if false noop - logs nothing. 49 | */ 50 | logger: true as boolean | ((query: string) => unknown), 51 | /** 52 | * Remove backticks. 53 | */ 54 | unescape: true, 55 | /** 56 | * Color of query (ANSI escape code) 57 | */ 58 | colorQuery: undefined as undefined | string, 59 | /** 60 | * Color of parameters (ANSI escape code) 61 | */ 62 | colorParameter: undefined as undefined | string, 63 | /** 64 | * Format SQL query, 65 | * colorQuery/colorParameter will be ignored. 66 | */ 67 | format: false, 68 | 69 | /** 70 | * Formatter options 71 | * https://github.com/mtxr/vscode-sqltools/tree/master/packages/formatter#options 72 | */ 73 | 74 | /** 75 | * Show Query Duration, default is false 76 | */ 77 | queryDuration: false as boolean, 78 | 79 | /** 80 | * Query language, default is Standard SQL 81 | */ 82 | language: undefined as 'sql' | 'n1ql' | 'db2' | 'pl/sql' | undefined, 83 | /** 84 | * Characters used for indentation 85 | */ 86 | indent: ' ', 87 | /** 88 | * How to change the case of reserved words 89 | */ 90 | // eslint-disable-next-line unicorn/no-null 91 | reservedWordCase: null as 'upper' | 'lower' | null, 92 | /** 93 | * How many line breaks between queries 94 | */ 95 | linesBetweenQueries: 1 as number | 'preserve', 96 | }; 97 | ``` 98 | 99 | ## Other projects 100 | 101 | https://github.com/unlight/nestolog - Logger for NestJS, implements `LoggerService` 102 | 103 | ## Screenshots 104 | 105 | #### Before 106 | 107 | ![](docs/before.png) 108 | 109 | #### After 110 | 111 | ![](docs/after.png) 112 | 113 | ## Development 114 | 115 | ```sh 116 | docker run -it -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=user postgres 117 | docker run -it -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=query_log_example_db mysql 118 | ``` 119 | -------------------------------------------------------------------------------- /Taskfile: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PATH="$PWD/node_modules/.bin":$PATH 3 | set -e 4 | 5 | build_tsup() { 6 | set -x 7 | rm -rfv dist 8 | # npm i tsup --no-save on CI/CD (uses esbuild no support of emitDecoratorMetadata) 9 | tsup src/index.ts --format cjs --target es2020 --dts --sourcemap 10 | cp -fv README.md package.json dist 11 | } 12 | 13 | "$@" 14 | -------------------------------------------------------------------------------- /docs/after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unlight/prisma-query-log/6155017251b00682e289be42c80d32f3020421a3/docs/after.png -------------------------------------------------------------------------------- /docs/before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unlight/prisma-query-log/6155017251b00682e289be42c80d32f3020421a3/docs/before.png -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import 'eslint-plugin-only-warn'; 2 | 3 | import globals from 'globals'; 4 | import pluginJs from '@eslint/js'; 5 | import tseslint from 'typescript-eslint'; 6 | import prettier from 'eslint-plugin-prettier/recommended'; 7 | import * as unicorn from 'eslint-plugin-unicorn'; 8 | import perfectionist from 'eslint-plugin-perfectionist'; 9 | import { fixupPluginRules } from '@eslint/compat'; 10 | 11 | /** @type {import('@typescript-eslint/utils').TSESLint.FlatConfig.ConfigFile} */ 12 | export default [ 13 | pluginJs.configs.recommended, 14 | ...tseslint.configs.recommended, 15 | ...tseslint.configs.recommendedTypeChecked, 16 | prettier, 17 | { 18 | ignores: [ 19 | 'dist/', 20 | 'coverage/', 21 | '@generated/**', 22 | '*.config.[cm]js', 23 | '.*rc.js', 24 | ], 25 | languageOptions: { 26 | globals: globals.node, 27 | parserOptions: { 28 | project: ['./tsconfig.json'], 29 | warnOnUnsupportedTypeScriptVersion: false, 30 | tsconfigRootDir: import.meta.dirname, 31 | }, 32 | }, 33 | rules: { 34 | 'max-lines': [1, { max: 300 }], 35 | 'max-params': [1, { max: 5 }], 36 | 'no-unneeded-ternary': [1], 37 | }, 38 | }, 39 | { 40 | ...unicorn.configs['flat/recommended'], 41 | rules: { 42 | 'unicorn/prevent-abbreviations': [ 43 | 'warn', 44 | { 45 | replacements: { 46 | args: false, 47 | }, 48 | }, 49 | ], 50 | }, 51 | }, 52 | { 53 | plugins: { 54 | perfectionist, 55 | }, 56 | rules: { 57 | 'perfectionist/sort-objects': [ 58 | 'warn', 59 | { 60 | type: 'natural', 61 | order: 'asc', 62 | }, 63 | ], 64 | }, 65 | }, 66 | { 67 | files: ['**/*.spec.ts', '**/*.e2e-spec.ts'], 68 | rules: { 69 | 'consistent-return': 0, 70 | 'max-lines': 0, 71 | '@typescript-eslint/no-explicit-any': 0, 72 | '@typescript-eslint/no-floating-promises': 0, 73 | '@typescript-eslint/no-non-null-assertion': 0, 74 | '@typescript-eslint/camelcase': 0, 75 | 'import/max-dependencies': 0, 76 | }, 77 | }, 78 | ]; 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prisma-query-log", 3 | "version": "0.0.0-dev", 4 | "license": "MIT", 5 | "description": "Log prisma query event", 6 | "main": "index.js", 7 | "typings": "index.d.ts", 8 | "keywords": [], 9 | "scripts": { 10 | "test": "npm run tscheck && npm run eslint && npm run test:r", 11 | "test:r": "vitest src/index.spec.ts", 12 | "test:w": "npm run test:r -- --watch", 13 | "eslint": "node node_modules/eslint/bin/eslint \"src/**/*.{ts,tsx}\"", 14 | "eslint:w": "watchexec -w src \"npm run eslint\"", 15 | "eslint:fix": "npm run eslint -- --fix", 16 | "tscheck": "tsc --noEmit", 17 | "tscheck:w": "npm run tscheck -- --watch", 18 | "format:src": "prettier src --write", 19 | "lint:commit": "sh Taskfile commit_lint", 20 | "commit": "cz", 21 | "build": "sh Taskfile build_tsup" 22 | }, 23 | "dependencies": { 24 | "@sqltools/formatter": "^1.2.5" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/unlight/prisma-query-log.git" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/unlight/prisma-query-log/issues" 32 | }, 33 | "homepage": "https://github.com/unlight/prisma-query-log#readme", 34 | "devDependencies": { 35 | "@commitlint/cli": "^19.3.0", 36 | "@commitlint/config-conventional": "^19.2.2", 37 | "@eslint/compat": "^1.1.0", 38 | "@prisma/client": "^5.16.1", 39 | "@semantic-release/changelog": "^6.0.3", 40 | "@semantic-release/git": "^10.0.1", 41 | "@types/node": "^20.14.9", 42 | "commitizen": "^4.3.0", 43 | "conventional-changelog-conventionalcommits": "^8.0.0", 44 | "cz-conventional-changelog": "^3.3.0", 45 | "eslint": "^9.6.0", 46 | "eslint-config-prettier": "^9.1.0", 47 | "eslint-plugin-only-warn": "^1.1.0", 48 | "eslint-plugin-perfectionist": "^2.11.0", 49 | "eslint-plugin-prettier": "^5.1.3", 50 | "eslint-plugin-unicorn": "^54.0.0", 51 | "globals": "^15.7.0", 52 | "precise-commits": "^1.0.2", 53 | "prettier": "^3.3.2", 54 | "prisma": "^5.16.1", 55 | "request": "^2.88.2", 56 | "semantic-release": "^24.0.0", 57 | "strip-ansi": "6.X", 58 | "ts-node": "^10.9.2", 59 | "tslib": "^2.6.3", 60 | "tsup": "^8.1.0", 61 | "typescript": "^5.5.2", 62 | "typescript-eslint": "^7.15.0", 63 | "vite": "^5.3.2", 64 | "vitest": "1.6.0", 65 | "watchexec-bin": "^1.0.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /prettier.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | trailingComma: 'all', 4 | tabWidth: 2, 5 | semi: true, 6 | singleQuote: true, 7 | arrowParens: 'avoid', 8 | }; 9 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | // provider = "sqlite" 7 | // url = "file:./dev.db" 8 | provider = "postgresql" 9 | url = "postgresql://user:postgres@127.0.0.1:5432/query_log_example_db" 10 | // provider = "mysql" 11 | // url = "mysql://root:password@127.0.0.1:3306/query_log_example_db" 12 | } 13 | 14 | model User { 15 | id Int @id @default(autoincrement()) 16 | email String @unique 17 | name String? 18 | posts Post[] 19 | } 20 | 21 | model Post { 22 | id Int @id @default(autoincrement()) 23 | title String 24 | content String? 25 | published Boolean @default(false) 26 | author User? @relation(fields: [authorId], references: [id]) 27 | authorId Int? 28 | createdAt DateTime @default(now()) 29 | updatedAt DateTime @updatedAt 30 | } 31 | -------------------------------------------------------------------------------- /prisma/script.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client'; 2 | 3 | const prisma = new PrismaClient({ 4 | log: [ 5 | { 6 | level: 'query', 7 | emit: 'event', 8 | }, 9 | ], 10 | }); 11 | 12 | prisma.$on('query', console.log); 13 | 14 | // A `main` function so that we can use async/await 15 | async function main() { 16 | // Seed the database with users and posts 17 | await prisma.post.create({ 18 | data: { 19 | title: `she will give him ten days of "love," at...`, 20 | content: `wow "content"`, 21 | }, 22 | }); 23 | 24 | const user1 = await prisma.user.create({ 25 | data: { 26 | email: 'alice@prisma.io', 27 | name: 'Alice', 28 | posts: { 29 | create: { 30 | title: 'Watch the talks from Prisma Day 2019', 31 | content: 'https://www.prisma.io/blog/z11sg6ipb3i1/', 32 | published: true, 33 | }, 34 | }, 35 | }, 36 | include: { 37 | posts: true, 38 | }, 39 | }); 40 | const user2 = await prisma.user.create({ 41 | data: { 42 | email: 'bob@prisma.io', 43 | name: 'Bob', 44 | posts: { 45 | create: [ 46 | { 47 | title: 'Subscribe to GraphQL Weekly for community news', 48 | content: 'https://graphqlweekly.com/', 49 | published: true, 50 | }, 51 | { 52 | title: 'Follow Prisma on Twitter', 53 | content: 'https://twitter.com/prisma/', 54 | published: false, 55 | }, 56 | ], 57 | }, 58 | }, 59 | include: { 60 | posts: true, 61 | }, 62 | }); 63 | console.log( 64 | `Created users: ${user1.name} (${user1.posts.length} post) and ${user2.name} (${user2.posts.length} posts) `, 65 | ); 66 | 67 | // Retrieve all published posts 68 | const allPosts = await prisma.post.findMany({ 69 | where: { published: true }, 70 | }); 71 | console.log(`Retrieved all published posts: ${allPosts}`); 72 | 73 | // Create a new post (written by an already existing user with email alice@prisma.io) 74 | const newPost = await prisma.post.create({ 75 | data: { 76 | title: 'Join the Prisma Slack community', 77 | content: 'http://slack.prisma.io', 78 | published: false, 79 | author: { 80 | connect: { 81 | email: 'alice@prisma.io', 82 | }, 83 | }, 84 | }, 85 | }); 86 | console.log(`Created a new post: ${newPost}`); 87 | 88 | // Publish the new post 89 | const updatedPost = await prisma.post.update({ 90 | where: { 91 | id: newPost.id, 92 | }, 93 | data: { 94 | published: true, 95 | }, 96 | }); 97 | console.log(`Published the newly created post: ${updatedPost}`); 98 | 99 | // Retrieve all posts by user with email alice@prisma.io 100 | const postsByUser = await prisma.user 101 | .findUnique({ 102 | where: { 103 | email: 'alice@prisma.io', 104 | }, 105 | }) 106 | .posts(); 107 | console.log(`Retrieved all posts from a specific user: ${postsByUser}`); 108 | } 109 | 110 | main() 111 | .catch(e => { 112 | console.error(e); 113 | process.exit(1); 114 | }) 115 | .finally(async () => { 116 | await prisma.$disconnect(); 117 | }); 118 | -------------------------------------------------------------------------------- /release.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | [ 4 | '@semantic-release/commit-analyzer', 5 | { 6 | preset: 'conventionalcommits', 7 | }, 8 | ], 9 | [ 10 | '@semantic-release/release-notes-generator', 11 | { 12 | preset: 'conventionalcommits', 13 | }, 14 | ], 15 | '@semantic-release/changelog', 16 | [ 17 | '@semantic-release/npm', 18 | { 19 | pkgRoot: 'dist', 20 | }, 21 | ], 22 | '@semantic-release/github', 23 | '@semantic-release/git', 24 | ], 25 | }; 26 | -------------------------------------------------------------------------------- /src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import stripAnsi from 'strip-ansi'; 2 | import { expect, it } from 'vitest'; 3 | 4 | import { createPrismaQueryEventHandler, PrismaQueryEvent } from '.'; 5 | 6 | const basePrismaQueryEvent: PrismaQueryEvent = { 7 | duration: 0, 8 | params: '[]', 9 | query: 'SELECT 1', 10 | target: 'quaint::connector::metrics', 11 | timestamp: new Date(), 12 | }; 13 | 14 | it('smoke', () => { 15 | expect(typeof createPrismaQueryEventHandler).toEqual('function'); 16 | }); 17 | 18 | it('should return function', () => { 19 | const log = createPrismaQueryEventHandler(); 20 | expect(typeof log).toEqual('function'); 21 | }); 22 | 23 | it('logger disabled noop', () => { 24 | const log = createPrismaQueryEventHandler({ logger: false }); 25 | expect(typeof log).toBe('function'); 26 | expect(log).toBe(Function.prototype); 27 | expect(() => log(basePrismaQueryEvent)).not.toThrow(); 28 | }); 29 | 30 | it('empty parameters', () => { 31 | let query = ''; 32 | const log = createPrismaQueryEventHandler({ 33 | format: false, 34 | logger: (q: string) => (query = q), 35 | }); 36 | const event = { 37 | ...basePrismaQueryEvent, 38 | params: '[]', 39 | query: 'SELECT 1', 40 | }; 41 | log(event); 42 | expect(query).toEqual('SELECT 1'); 43 | }); 44 | 45 | it('replace parameters default', () => { 46 | let query = ''; 47 | const log = createPrismaQueryEventHandler({ 48 | format: false, 49 | logger: (q: string) => (query = q), 50 | unescape: false, 51 | }); 52 | const event = { 53 | ...basePrismaQueryEvent, 54 | params: '[1,0]', 55 | query: 'SELECT VERSION() LIMIT ? OFFSET ?', 56 | }; 57 | log(event); 58 | expect(query).toEqual('SELECT VERSION() LIMIT 1 OFFSET 0'); 59 | }); 60 | 61 | it('replace parameters format', () => { 62 | let query = ''; 63 | const log = createPrismaQueryEventHandler({ 64 | format: true, 65 | logger: (q: string) => (query = q), 66 | unescape: false, 67 | }); 68 | const event = { 69 | ...basePrismaQueryEvent, 70 | params: '[1,"A"]', 71 | query: 'SELECT ?, ?', 72 | }; 73 | log(event); 74 | expect(query).toEqual(`SELECT 1, 75 | "A"`); 76 | }); 77 | 78 | it('unescape fields', () => { 79 | let query = ''; 80 | const log = createPrismaQueryEventHandler({ 81 | format: false, 82 | logger: (q: string) => (query = q), 83 | unescape: true, 84 | }); 85 | const event = { 86 | ...basePrismaQueryEvent, 87 | params: '["1"]', 88 | query: 89 | 'SELECT `data`.`Article`.`articleId` FROM `data`.`Article` WHERE `data`.`Article`.`articleId` = ?', 90 | }; 91 | log(event); 92 | expect(query).toEqual( 93 | 'SELECT Article.articleId FROM Article WHERE Article.articleId = "1"', 94 | ); 95 | }); 96 | 97 | it('colorize query', () => { 98 | let query = ''; 99 | const colorQuery = '\u001B[96m'; 100 | const colorParameter = '\u001B[90m'; 101 | const log = createPrismaQueryEventHandler({ 102 | colorParameter, 103 | colorQuery, 104 | format: false, 105 | logger: (q: string) => (query = q), 106 | unescape: true, 107 | }); 108 | const event = { 109 | ...basePrismaQueryEvent, 110 | params: '["A"]', 111 | query: 'SELECT ?', 112 | }; 113 | log(event); 114 | expect(query).toEqual( 115 | colorQuery + 116 | 'SELECT ' + 117 | colorParameter + 118 | '"A"' + 119 | '\u001B[0m' + 120 | colorQuery + 121 | '\u001B[0m', 122 | ); 123 | }); 124 | 125 | it('parse date parameter', () => { 126 | let query = ''; 127 | const log = createPrismaQueryEventHandler({ 128 | format: false, 129 | logger: (q: string) => (query = q), 130 | unescape: false, 131 | }); 132 | const event = { 133 | ...basePrismaQueryEvent, 134 | params: '["1",2020-12-25 19:35:06.803149100 UTC, 2021-10-01 00:00:00 UTC]', 135 | query: 136 | 'INSERT INTO Comment (commentId, createdAt, updatedAt) VALUES (?,?,?)', 137 | }; 138 | log(event); 139 | expect(query).toEqual( 140 | 'INSERT INTO Comment (commentId, createdAt, updatedAt) VALUES ("1", "2020-12-25 19:35:06.803149100 UTC", "2021-10-01 00:00:00 UTC")', 141 | ); 142 | }); 143 | 144 | it('update single statement', () => { 145 | let query = ''; 146 | const log = createPrismaQueryEventHandler({ 147 | format: false, 148 | logger: (q: string) => (query = q), 149 | unescape: true, 150 | }); 151 | const event = { 152 | ...basePrismaQueryEvent, 153 | params: '[2020-12-25 20:02:45.589918800 UTC,"body","1"]', 154 | query: 155 | 'UPDATE `data`.`Article` SET `updatedAt` = ?, `body` = ? WHERE `data`.`Article`.`articleId` IN (?)', 156 | }; 157 | log(event); 158 | expect(query).toEqual( 159 | 'UPDATE Article SET updatedAt = "2020-12-25 20:02:45.589918800 UTC", body = "body" WHERE Article.articleId IN ("1")', 160 | ); 161 | }); 162 | 163 | it('format sql defaults', () => { 164 | let query = ''; 165 | const log = createPrismaQueryEventHandler({ 166 | format: true, 167 | logger: (q: string) => (query = q), 168 | }); 169 | const event = { 170 | ...basePrismaQueryEvent, 171 | params: '[]', 172 | query: 'SELECT EmployeeId, FirstName FROM Employees', 173 | }; 174 | log(event); 175 | expect(query).toEqual(`SELECT EmployeeId,\n FirstName\nFROM Employees`); 176 | }); 177 | 178 | it('format join query', () => { 179 | let query = ''; 180 | const log = createPrismaQueryEventHandler({ 181 | format: true, 182 | logger: (q: string) => (query = q), 183 | }); 184 | const event = { 185 | ...basePrismaQueryEvent, 186 | params: '[1,"B"]', 187 | query: `SELECT * FROM Someplace S JOIN Elsewhere AS E WITH (HOLDLOCK) ON 1=1 and X = ? and Y = ?`, 188 | }; 189 | log(event); 190 | expect(query).toEqual( 191 | `SELECT *\nFROM Someplace S\n JOIN Elsewhere AS E WITH (HOLDLOCK) ON 1 = 1\n and X = 1\n and Y = "B"`, 192 | ); 193 | }); 194 | 195 | it('format with color', () => { 196 | let query = ''; 197 | const log = createPrismaQueryEventHandler({ 198 | colorParameter: '\u001B[90m', 199 | colorQuery: '\u001B[96m', 200 | format: true, 201 | logger: (q: string) => (query = q), 202 | }); 203 | const event = { 204 | ...basePrismaQueryEvent, 205 | params: '[1]', 206 | query: `SELECT * FROM Someplace S WHERE X = ?`, 207 | }; 208 | log(event); 209 | expect(query).toEqual(`SELECT * 210 | FROM Someplace S 211 | WHERE X = 1`); 212 | }); 213 | 214 | it('long parameters', () => { 215 | let query = ''; 216 | const log = createPrismaQueryEventHandler({ 217 | format: true, 218 | logger: (q: string) => (query = q), 219 | }); 220 | const event = { 221 | ...basePrismaQueryEvent, 222 | params: 223 | '["cki4upcor0036jov4h6hab7qi", "cki4upcor0037jov4y4syn2bg", "cki4upcor0038jov46rrlfy2a", "cki4upcor0039jov49sm73sfa"]', 224 | query: `SELECT Tag.tagId FROM Tag WHERE Tag.tagId IN (?,?,?,?)`, 225 | }; 226 | log(event); 227 | expect(query).toEqual(`SELECT Tag.tagId 228 | FROM Tag 229 | WHERE Tag.tagId IN ( 230 | "cki4upcor0036jov4h6hab7qi", 231 | "cki4upcor0037jov4y4syn2bg", 232 | "cki4upcor0038jov46rrlfy2a", 233 | "cki4upcor0039jov49sm73sfa" 234 | )`); 235 | }); 236 | 237 | it('comma between parameters without color', () => { 238 | let query = ''; 239 | const log = createPrismaQueryEventHandler({ 240 | format: false, 241 | logger: (q: string) => (query = q), 242 | }); 243 | const event = { 244 | ...basePrismaQueryEvent, 245 | params: '["1", "2", "3"]', 246 | query: 'SELECT 1 WHERE `data`.`Article`.`articleId` IN (?,?,?)', 247 | }; 248 | log(event); 249 | expect(query).toContain('articleId IN ("1", "2", "3")'); 250 | }); 251 | 252 | it('comma between parameters with color', () => { 253 | let query = ''; 254 | const log = createPrismaQueryEventHandler({ 255 | colorParameter: '\u001B[90m', 256 | colorQuery: '\u001B[96m', 257 | format: false, 258 | logger: (q: string) => (query = q), 259 | }); 260 | const event = { 261 | ...basePrismaQueryEvent, 262 | params: '["1", "2", "3"]', 263 | query: 'SELECT 1 WHERE `data`.`Article`.`articleId` IN (?,?,?)', 264 | }; 265 | log(event); 266 | expect(stripAnsi(query)).toContain('articleId IN ("1", "2", "3")'); 267 | }); 268 | 269 | it('mysql select', () => { 270 | let query = ''; 271 | const log = createPrismaQueryEventHandler({ 272 | format: false, 273 | logger: (q: string) => (query = q), 274 | }); 275 | const event = { 276 | ...basePrismaQueryEvent, 277 | params: '[true]', 278 | query: 279 | 'SELECT `query_log_example_db`.`Post`.`id`, `query_log_example_db`.`Post`.`updatedAt` FROM `query_log_example_db`.`Post` WHERE `query_log_example_db`.`Post`.`published` = ?', 280 | }; 281 | log(event); 282 | 283 | expect(query).toEqual( 284 | 'SELECT Post.id, Post.updatedAt FROM Post WHERE Post.published = true', 285 | ); 286 | }); 287 | 288 | it('postgres select', () => { 289 | let query = ''; 290 | const log = createPrismaQueryEventHandler({ 291 | format: false, 292 | logger: (q: string) => (query = q), 293 | }); 294 | const event = { 295 | ...basePrismaQueryEvent, 296 | params: '[true,0]', 297 | query: 298 | 'SELECT "public"."Post"."id", "public"."Post"."createdAt" FROM "public"."Post" WHERE "public"."Post"."published" = $1 OFFSET $2', 299 | }; 300 | log(event); 301 | 302 | expect(query).toEqual( 303 | 'SELECT "Post"."id", "Post"."createdAt" FROM "Post" WHERE "Post"."published" = true OFFSET 0', 304 | ); 305 | }); 306 | 307 | it('postgres insert strings', () => { 308 | let query = ''; 309 | const log = createPrismaQueryEventHandler({ 310 | format: false, 311 | logger: (q: string) => (query = q), 312 | }); 313 | const event = { 314 | ...basePrismaQueryEvent, 315 | params: '["alice@prisma.io","Alice"]', 316 | query: 317 | 'INSERT INTO "public"."User" ("email","name") VALUES ($1,$2) RETURNING "public"."User"."id"', 318 | }; 319 | log(event); 320 | 321 | expect(query).toEqual( 322 | 'INSERT INTO "User" ("email","name") VALUES ("alice@prisma.io", "Alice") RETURNING "User"."id"', 323 | ); 324 | }); 325 | 326 | it('update postgres', () => { 327 | let query = ''; 328 | const log = createPrismaQueryEventHandler({ 329 | format: false, 330 | logger: (q: string) => (query = q), 331 | unescape: true, 332 | }); 333 | const event = { 334 | ...basePrismaQueryEvent, 335 | params: '[true,2021-12-21 16:56:47.280795800 UTC,4]', 336 | query: 337 | 'UPDATE "public"."Post" SET "published" = $1, "updatedAt" = $2 WHERE "public"."Post"."id" IN ($3)', 338 | }; 339 | log(event); 340 | expect(query).toEqual( 341 | 'UPDATE "Post" SET "published" = true, "updatedAt" = "2021-12-21 16:56:47.280795800 UTC" WHERE "Post"."id" IN (4)', 342 | ); 343 | }); 344 | 345 | it('backticked string with double quotes', () => { 346 | let query = ''; 347 | const log = createPrismaQueryEventHandler({ 348 | format: false, 349 | logger: (q: string) => (query = q), 350 | unescape: true, 351 | }); 352 | const event = { 353 | ...basePrismaQueryEvent, 354 | params: '["she will give him ten days of "love," at...","wow "content","]', 355 | query: 356 | 'INSERT INTO "public"."Post" ("title") VALUES ($1) RETURNING "public"."Post"."id"', 357 | }; 358 | log(event); 359 | expect(query).toEqual( 360 | 'INSERT INTO "Post" ("title") VALUES ($1) RETURNING "Post"."id"', 361 | ); 362 | }); 363 | 364 | it('postgres insert with date escaped', async () => { 365 | let query = ''; 366 | const log = createPrismaQueryEventHandler({ 367 | format: false, 368 | logger: (q: string) => (query = q), 369 | unescape: true, 370 | }); 371 | const event = { 372 | ...basePrismaQueryEvent, 373 | params: '[1,"2024-07-01 17:06:41.583 UTC"]', 374 | query: `INSERT INTO "public"."Recipe" ("_id", "creationDate") VALUES ($1,$2) RETURNING "public"."Recipe"."_id", "public"."Recipe"."creationDate"`, 375 | }; 376 | log(event); 377 | expect(query).not.toContain('$1'); 378 | expect(query).not.toContain('$2'); 379 | expect(query).toEqual( 380 | 'INSERT INTO "Recipe" ("_id", "creationDate") VALUES (1, "2024-07-01 17:06:41.583 UTC") RETURNING "Recipe"."_id", "Recipe"."creationDate"', 381 | ); 382 | }); 383 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { defaultOptions } from './options'; 2 | 3 | export type PrismaQueryEvent = { 4 | timestamp: Date; 5 | query: string; 6 | params: string; 7 | duration: number; 8 | target: string; 9 | }; 10 | 11 | type CreatePrismaQueryEventHandlerArgs = typeof defaultOptions; 12 | 13 | let formatter: { format: (query: string, options) => string }; 14 | 15 | export function createPrismaQueryEventHandler( 16 | args: Partial = {}, 17 | ): (event: PrismaQueryEvent) => void { 18 | const options = { ...defaultOptions, ...args }; 19 | const logger = options.logger === true ? console.log : options.logger; 20 | if (typeof logger !== 'function') { 21 | return Function.prototype as (event: PrismaQueryEvent) => void; // noop 22 | } 23 | const { format, unescape } = options; 24 | const colorQuery = format ? false : options.colorQuery; 25 | const colorParameter = options.colorParameter ?? colorQuery; 26 | 27 | return function prismaQueryLog(event: PrismaQueryEvent) { 28 | let query = event.query; 29 | const parameters = parseParameters(event.params); 30 | if (unescape) { 31 | query = unescapeQuery(query); 32 | } 33 | 34 | if (parameters.length > 0) { 35 | query = query.replace( 36 | /(\?|\$\d+)/g, 37 | (match, p1, offset, string: string) => { 38 | let parameter = JSON.stringify(parameters.shift()); 39 | const previousChar = string.charAt(offset - 1); 40 | if (colorQuery && colorParameter) { 41 | parameter = colorParameter + parameter + '\u001B[0m' + colorQuery; 42 | } 43 | 44 | return (previousChar === ',' ? ' ' : '') + parameter; 45 | }, 46 | ); 47 | } 48 | 49 | if (format) { 50 | if (!(formatter as typeof formatter | undefined)) { 51 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 52 | formatter = require('@sqltools/formatter'); 53 | } 54 | query = formatter.format(query, options).trim(); 55 | } 56 | 57 | if (colorQuery && colorParameter) { 58 | query = colorQuery + query + '\u001B[0m'; 59 | } 60 | 61 | logger(query); 62 | }; 63 | } 64 | 65 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 66 | function parseParameters(parameters: string): any[] { 67 | parameters = parameters.replace( 68 | /([^"])(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.?\d* UTC)([^"])/g, 69 | '$1"$2"$3', 70 | ); 71 | 72 | try { 73 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return 74 | return JSON.parse(parameters); 75 | } catch { 76 | return []; 77 | } 78 | } 79 | 80 | function unescapeQuery(query: string) { 81 | const regex = /(?["`])\w+["`](\.["`]\w+["`])?(\.["`]\w+["`])?/g; 82 | const matchAllResult = query.matchAll(regex); 83 | const matches = Array.from(matchAllResult); 84 | for (let index = matches.length - 1; index >= 0; index--) { 85 | const { 86 | 0: fullMatch, 87 | groups: { quote } = {}, 88 | index: matchIndex, 89 | } = matches[index]!; 90 | if (!matchIndex || !fullMatch) { 91 | continue; 92 | } 93 | let replacement = fullMatch.replace( 94 | /["`]\w+["`]\.(["`]\w+["`](\.["`]\w+["`])?)/g, 95 | '$1', 96 | ); 97 | if (quote === '`') { 98 | const parts = replacement.split('`.`'); 99 | replacement = parts.join('.').slice(1, -1); 100 | } 101 | query = 102 | query.slice(0, matchIndex) + 103 | replacement + 104 | query.slice(matchIndex + fullMatch.length); 105 | } 106 | 107 | return query; 108 | } 109 | -------------------------------------------------------------------------------- /src/options.ts: -------------------------------------------------------------------------------- 1 | export const defaultOptions = { 2 | /** 3 | * Color of parameters (ANSI escape code) 4 | */ 5 | colorParameter: undefined as undefined | string, 6 | /** 7 | * Color of query (ANSI escape code) 8 | */ 9 | colorQuery: undefined as undefined | string, 10 | /** 11 | * Format SQL query, 12 | * colorQuery/colorParameter will be ignored. 13 | */ 14 | format: false, 15 | /** 16 | * Characters used for indentation 17 | */ 18 | indent: ' ', 19 | /** 20 | * Query language, default is Standard SQL 21 | */ 22 | language: undefined as 'sql' | 'n1ql' | 'db2' | 'pl/sql' | undefined, 23 | 24 | /** 25 | * Formatter options 26 | * https://github.com/mtxr/vscode-sqltools/tree/master/packages/formatter#options 27 | */ 28 | 29 | /** 30 | * How many line breaks between queries 31 | */ 32 | linesBetweenQueries: 1 as number | 'preserve', 33 | 34 | /** 35 | * Boolean of custom log function, 36 | * if true `console.log` will be used, 37 | * if false noop - logs nothing. 38 | */ 39 | logger: true as boolean | ((query: string) => unknown), 40 | /** 41 | * Show Query Duration, default is false 42 | */ 43 | queryDuration: false as boolean, 44 | /** 45 | * How to change the case of reserved words 46 | */ 47 | 48 | reservedWordCase: null as 'upper' | 'lower' | null, 49 | /** 50 | * Remove backticks. 51 | */ 52 | unescape: true, 53 | }; 54 | -------------------------------------------------------------------------------- /src/postgres.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { PrismaClient } from '@prisma/client'; 3 | 4 | async function main() { 5 | const prisma = new PrismaClient({ 6 | datasources: { 7 | db: { 8 | url: 'postgresql://user:postgres@127.0.0.1:5432/query_log_example_db', 9 | // url: 'mysql://root:password@127.0.0.1:3306/query_log_example_db', 10 | }, 11 | }, 12 | errorFormat: 'colorless', 13 | log: [ 14 | { 15 | emit: 'event', 16 | level: 'query', 17 | }, 18 | ], 19 | }); 20 | prisma.$on('query', event => { 21 | console.log(event); 22 | }); 23 | 24 | // Seed the database with users and posts 25 | const user1 = await prisma.user.create({ 26 | data: { 27 | email: 'alice@prisma.io', 28 | name: 'Alice', 29 | posts: { 30 | create: { 31 | content: 'https://www.prisma.io/blog/z11sg6ipb3i1/', 32 | published: true, 33 | title: 'Watch the talks from Prisma Day 2019', 34 | }, 35 | }, 36 | }, 37 | include: { 38 | posts: true, 39 | }, 40 | }); 41 | const user2 = await prisma.user.create({ 42 | data: { 43 | email: 'bob@prisma.io', 44 | name: 'Bob', 45 | posts: { 46 | create: [ 47 | { 48 | content: 'https://graphqlweekly.com/', 49 | published: true, 50 | title: 'Subscribe to GraphQL Weekly for community news', 51 | }, 52 | { 53 | content: 'https://twitter.com/prisma/', 54 | published: false, 55 | title: 'Follow Prisma on Twitter', 56 | }, 57 | ], 58 | }, 59 | }, 60 | include: { 61 | posts: true, 62 | }, 63 | }); 64 | 65 | // Retrieve all published posts 66 | const allPosts = await prisma.post.findMany({ 67 | where: { published: true }, 68 | }); 69 | 70 | // Create a new post (written by an already existing user with email alice@prisma.io) 71 | const newPost = await prisma.post.create({ 72 | data: { 73 | author: { 74 | connect: { 75 | email: 'alice@prisma.io', 76 | }, 77 | }, 78 | content: 'http://slack.prisma.io', 79 | published: false, 80 | title: 'Join the Prisma Slack community', 81 | }, 82 | }); 83 | 84 | // Publish the new post 85 | const updatedPost = await prisma.post.update({ 86 | data: { 87 | published: true, 88 | }, 89 | where: { 90 | id: newPost.id, 91 | }, 92 | }); 93 | 94 | // Retrieve all posts by user with email alice@prisma.io 95 | const postsByUser = await prisma.user 96 | .findUnique({ 97 | where: { 98 | email: 'alice@prisma.io', 99 | }, 100 | }) 101 | .posts(); 102 | } 103 | 104 | main(); 105 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ts-node": { 3 | "transpileOnly": true, 4 | "compilerOptions": { 5 | "target": "es2022", 6 | "module": "commonjs" 7 | } 8 | }, 9 | "compilerOptions": { 10 | "target": "es2022", 11 | "module": "commonjs", 12 | "noUncheckedIndexedAccess": true, 13 | "moduleResolution": "node", 14 | "importHelpers": true, 15 | "strict": true, 16 | "noImplicitAny": false, 17 | "experimentalDecorators": false, 18 | "emitDecoratorMetadata": false, 19 | "outDir": "dist", 20 | "pretty": true, 21 | "esModuleInterop": true, 22 | "removeComments": false, 23 | "sourceMap": true, 24 | "declaration": false, 25 | "declarationMap": false, 26 | "skipLibCheck": true, 27 | "lib": ["esnext"] 28 | }, 29 | "include": ["src"] 30 | } 31 | --------------------------------------------------------------------------------