-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(prisma): ✨ add
@bit-ocean/prisma
- Loading branch information
Showing
21 changed files
with
838 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# @bit-ocean/prisma | ||
|
||
![npm](https://img.shields.io/npm/v/@bit-ocean/prisma?logo=prisma&label=prisma) | ||
[![Made with Prisma](http://made-with.prisma.io/dark.svg)](https://prisma.io) | ||
|
||
> Universal Prisma module. | ||
## Installation | ||
|
||
```bash | ||
pnpm add @bit-ocean/prisma | ||
``` | ||
|
||
## License | ||
|
||
[MIT](/LICENSE) License © 2024 Bit Ocean |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
{ | ||
"name": "@bit-ocean/prisma", | ||
"version": "0.0.0", | ||
"description": "Universal Prisma module.", | ||
"author": "Bruce Song <[email protected]> (https://github.com/recallwei/)", | ||
"homepage": "https://github.com/bit-ocean-studio/infra#readme", | ||
"bugs": "https://github.com/bit-ocean-studio/infra/issues", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/bit-ocean-studio/infra.git", | ||
"directory": "packages/prisma" | ||
}, | ||
"keywords": [ | ||
"bit-ocean", | ||
"bit-ocean-infra", | ||
"prisma", | ||
"prisma-module", | ||
"nest-prisma", | ||
"nest-prisma-module" | ||
], | ||
"files": [ | ||
"dist" | ||
], | ||
"main": "dist/index.js", | ||
"module": "dist/index.mjs", | ||
"types": "dist/index.d.ts", | ||
"exports": { | ||
".": { | ||
"import": "./dist/index.mjs", | ||
"require": "./dist/index.js", | ||
"types": "./dist/index.d.ts" | ||
} | ||
}, | ||
"scripts": { | ||
"dev": "tsup --watch", | ||
"build": "tsup", | ||
"prebuild": "pnpm prisma generate", | ||
"type:check": "tsc --pretty --noEmit", | ||
"cspell:check": "cspell --no-progress --show-suggestions --show-context --cache **", | ||
"eslint:check": "eslint . --color --cache", | ||
"eslint:fix": "eslint . --color --cache --fix", | ||
"prettier:check": "prettier --check --cache --ignore-unknown --ignore-path=../../.prettierignore .", | ||
"prettier:fix": "prettier --write --cache --ignore-unknown --ignore-path=../../.prettierignore ." | ||
}, | ||
"peerDependencies": { | ||
"@nestjs/common": "^9.0.0 || ^10.0.0", | ||
"@nestjs/core": "^9.0.0 || ^10.0.0", | ||
"@prisma/client": "^5.0.0", | ||
"prisma": "^5.0.0", | ||
"reflect-metadata": "^0.2.2" | ||
}, | ||
"devDependencies": { | ||
"@nestjs/common": "^10.3.10", | ||
"@nestjs/core": "^10.3.10", | ||
"@prisma/client": "^5.17.0", | ||
"prisma": "^5.17.0", | ||
"reflect-metadata": "^0.2.2" | ||
}, | ||
"prisma": { | ||
"schema": "prisma/schema.prisma" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"license": "MIT" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
datasource db { | ||
provider = "sqlite" | ||
url = env("DATABASE_URL") | ||
} | ||
|
||
generator client { | ||
provider = "prisma-client-js" | ||
} | ||
|
||
model User { | ||
id Int @id @default(autoincrement()) | ||
name String? | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export * from './prisma.constants' | ||
export * from './prisma.interfaces' | ||
export * from './prisma.module' | ||
export * from './prisma.service' | ||
export * from './prisma-client-exception.filter' | ||
export * from './prisma-logging.middleware' | ||
export * from './soft-delete.extension' |
106 changes: 106 additions & 0 deletions
106
packages/prisma/src/basic/prisma-client-exception.filter.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { ArgumentsHost, Catch, HttpException, HttpServer, HttpStatus } from '@nestjs/common' | ||
import { APP_FILTER, BaseExceptionFilter, HttpAdapterHost } from '@nestjs/core' | ||
import { Prisma } from '@prisma/client' | ||
|
||
export declare type GqlContextType = 'graphql' | ||
|
||
export type ErrorCodesStatusMapping = { | ||
[key: string]: | ||
| number | ||
| { | ||
statusCode?: number | ||
errorMessage?: string | ||
} | ||
} | ||
|
||
@Catch(Prisma?.PrismaClientKnownRequestError) | ||
export class PrismaClientExceptionFilter extends BaseExceptionFilter { | ||
/** | ||
* Default error codes mapping. | ||
* Error codes definition for Prisma Client (Query Engine). | ||
* @see https://www.prisma.io/docs/reference/api-reference/error-reference#prisma-client-query-engine | ||
*/ | ||
private readonly defaultMapping = new Map([ | ||
['P2000', HttpStatus.BAD_REQUEST], | ||
['P2002', HttpStatus.CONFLICT], | ||
['P2025', HttpStatus.NOT_FOUND] | ||
]) | ||
|
||
private readonly userDefinedMapping?: ErrorCodesStatusMapping | ||
|
||
/** | ||
* @param applicationRef | ||
* @param errorCodesStatusMapping | ||
*/ | ||
constructor(applicationRef?: HttpServer, errorCodesStatusMapping?: ErrorCodesStatusMapping) { | ||
super(applicationRef) | ||
this.userDefinedMapping = errorCodesStatusMapping | ||
} | ||
|
||
catch(exception: Prisma.PrismaClientKnownRequestError, host: ArgumentsHost) { | ||
return this.catchClientKnownRequestError(exception, host) | ||
} | ||
|
||
private catchClientKnownRequestError( | ||
exception: Prisma.PrismaClientKnownRequestError, | ||
host: ArgumentsHost | ||
) { | ||
const statusCode = this.userDefinedStatusCode(exception) ?? this.defaultStatusCode(exception) | ||
|
||
const message = | ||
this.userDefinedExceptionMessage(exception) ?? this.defaultExceptionMessage(exception) | ||
|
||
if (host.getType() === 'http') { | ||
if (statusCode === undefined) { | ||
return super.catch(exception, host) | ||
} | ||
|
||
return super.catch(new HttpException({ statusCode, message }, statusCode), host) | ||
} | ||
if (host.getType<GqlContextType>() === 'graphql') { | ||
if (statusCode === undefined) { | ||
return exception | ||
} | ||
|
||
return new HttpException({ statusCode, message }, statusCode) | ||
} | ||
return super.catch(exception, host) | ||
} | ||
|
||
private userDefinedStatusCode( | ||
exception: Prisma.PrismaClientKnownRequestError | ||
): number | undefined { | ||
const userDefinedValue = this.userDefinedMapping?.[exception.code] | ||
return typeof userDefinedValue === 'number' ? userDefinedValue : userDefinedValue?.statusCode | ||
} | ||
|
||
private defaultStatusCode(exception: Prisma.PrismaClientKnownRequestError): number | undefined { | ||
return this.defaultMapping.get(exception.code) | ||
} | ||
|
||
private userDefinedExceptionMessage( | ||
exception: Prisma.PrismaClientKnownRequestError | ||
): string | undefined { | ||
const userDefinedValue = this.userDefinedMapping?.[exception.code] | ||
return typeof userDefinedValue === 'number' ? undefined : userDefinedValue?.errorMessage | ||
} | ||
|
||
private defaultExceptionMessage(exception: Prisma.PrismaClientKnownRequestError): string { | ||
const shortMessage = exception.message.substring(exception.message.indexOf('→')) | ||
return `[${exception.code}]: ${shortMessage | ||
.substring(shortMessage.indexOf('\n')) | ||
.replace(/\n/g, '') | ||
.trim()}` | ||
} | ||
} | ||
|
||
export function providePrismaClientExceptionFilter( | ||
errorCodesStatusMapping?: ErrorCodesStatusMapping | ||
) { | ||
return { | ||
provide: APP_FILTER, | ||
useFactory: ({ httpAdapter }: HttpAdapterHost) => | ||
new PrismaClientExceptionFilter(httpAdapter, errorCodesStatusMapping), | ||
inject: [HttpAdapterHost] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import type { Logger } from '@nestjs/common' | ||
import type { Prisma } from '@prisma/client' | ||
|
||
export interface LoggingMiddlewareOptions { | ||
logger: Console | Logger | ||
logLevel: 'log' | 'debug' | 'warn' | 'error' | ||
/** | ||
* Create a custom log message. | ||
*/ | ||
logMessage?: (query: QueryInfo) => string | ||
} | ||
|
||
export interface QueryInfo { | ||
/** | ||
* The queried prisma model. | ||
*/ | ||
model: string | ||
/** | ||
* The performed action on the model e.g. `create`, `findUnique`. | ||
*/ | ||
action: string | ||
/** | ||
* Time `Date.now()` before the query execution. | ||
* | ||
*/ | ||
before: number | ||
/** | ||
* Time `Date.now()` after the query execution. | ||
*/ | ||
after: number | ||
/** | ||
* Execution time of the query in milliseconds. | ||
*/ | ||
executionTime: number | ||
} | ||
|
||
export function loggingMiddleware( | ||
{ logger, logMessage, logLevel }: LoggingMiddlewareOptions = { | ||
logger: console, | ||
logLevel: 'debug' | ||
} | ||
): Prisma.Middleware { | ||
return async (params, next) => { | ||
const before = Date.now() | ||
|
||
const result = await next(params) | ||
|
||
const after = Date.now() | ||
|
||
const executionTime = after - before | ||
|
||
if (logMessage) { | ||
logger[logLevel]( | ||
logMessage({ | ||
model: params.model!, | ||
action: params.action, | ||
before, | ||
after, | ||
executionTime | ||
}) | ||
) | ||
} else { | ||
logger[logLevel](`Prisma Query ${params.model}.${params.action} took ${executionTime}ms`) | ||
} | ||
|
||
return result | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const PRISMA_SERVICE_OPTIONS = 'PRISMA_SERVICE_OPTIONS' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import type { ModuleMetadata, Type } from '@nestjs/common' | ||
import type { Prisma } from '@prisma/client' | ||
|
||
export interface PrismaModuleOptions { | ||
/** | ||
* If `true`, registers `PrismaModule` as a global module. | ||
* @See https://docs.nestjs.com/modules#global-modules | ||
*/ | ||
isGlobal?: boolean | ||
/** | ||
* Options for `PrismaService`. | ||
*/ | ||
prismaServiceOptions?: PrismaServiceOptions | ||
} | ||
|
||
export interface PrismaServiceOptions { | ||
/** | ||
* Pass options directly to the `PrismaClient`. | ||
* @see https://www.prisma.io/docs/reference/api-reference/prisma-client-reference/#prismaclient | ||
*/ | ||
prismaOptions?: Prisma.PrismaClientOptions | ||
/** | ||
* If `true`, the `PrismaClient` explicitly creates a connection pool and your first query will respond instantly. | ||
* For most use cases the lazy connect behavior of `PrismaClient` will do. The first query of `PrismaClient` creates the connection pool. | ||
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-management | ||
*/ | ||
explicitConnect?: boolean | ||
/** | ||
* Apply Prisma middlewares to perform actions before or after db queries. | ||
* @deprecated | ||
* @see https://www.prisma.io/docs/orm/prisma-client/client-extensions/middleware | ||
*/ | ||
middlewares?: Prisma.Middleware[] | ||
} | ||
|
||
export interface PrismaOptionsFactory { | ||
createPrismaOptions: () => PrismaServiceOptions | Promise<PrismaServiceOptions> | ||
} | ||
|
||
export interface PrismaModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> { | ||
/** | ||
* If `true`, registers `PrismaModule` as a global module. | ||
* @See https://docs.nestjs.com/modules#global-modules | ||
*/ | ||
isGlobal?: boolean | ||
useExisting?: Type<PrismaOptionsFactory> | ||
useClass?: Type<PrismaOptionsFactory> | ||
useFactory?: (...args: any[]) => PrismaServiceOptions | Promise<PrismaServiceOptions> | ||
inject?: any[] | ||
} |
Oops, something went wrong.