@fluojs/prisma 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +70 -48
- package/README.md +68 -45
- package/dist/module.d.ts +2 -2
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +11 -17
- package/dist/service.d.ts +27 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +59 -0
- package/dist/transaction-decorator-contract.notes.d.ts +19 -0
- package/dist/transaction-decorator-contract.notes.d.ts.map +1 -0
- package/dist/transaction-decorator-contract.notes.js +1 -0
- package/dist/transaction.d.ts +17 -20
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +63 -34
- package/package.json +4 -4
package/README.ko.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
|
|
4
4
|
|
|
5
|
-
fluo 애플리케이션을 위한 Prisma
|
|
5
|
+
fluo 애플리케이션을 위한 Prisma lifecycle 및 ALS 기반 transaction context입니다. `PrismaClient`를 모듈 시스템에 연결하고 자동 연결 관리와 요청 범위 트랜잭션을 제공합니다.
|
|
6
6
|
|
|
7
7
|
## 목차
|
|
8
8
|
|
|
@@ -10,10 +10,9 @@ fluo 애플리케이션을 위한 Prisma 라이프사이클 및 ALS 기반 트
|
|
|
10
10
|
- [사용 시점](#사용-시점)
|
|
11
11
|
- [빠른 시작](#빠른-시작)
|
|
12
12
|
- [공통 패턴](#공통-패턴)
|
|
13
|
-
- [
|
|
13
|
+
- [서비스 트랜잭션 경계 (@Transaction)](#서비스-트랜잭션-경계-transaction)
|
|
14
14
|
- [여러 클라이언트를 위한 이름 있는 등록](#여러-클라이언트를-위한-이름-있는-등록)
|
|
15
|
-
- [수동
|
|
16
|
-
- [자동 요청 트랜잭션](#자동-요청-트랜잭션)
|
|
15
|
+
- [수동 트랜잭션과 current()](#수동-트랜잭션과-current)
|
|
17
16
|
- [종료와 status 계약](#종료와-status-계약)
|
|
18
17
|
- [비동기 설정과 격리](#비동기-설정과-격리)
|
|
19
18
|
- [수동 모듈 조합](#수동-모듈-조합)
|
|
@@ -56,33 +55,52 @@ class AppModule {}
|
|
|
56
55
|
|
|
57
56
|
## 공통 패턴
|
|
58
57
|
|
|
59
|
-
###
|
|
58
|
+
### 서비스 트랜잭션 경계 (@Transaction)
|
|
60
59
|
|
|
61
|
-
`
|
|
60
|
+
`@Transaction()` 데코레이터는 서비스 레이어에서 트랜잭션 경계를 정의하는 권장 방법입니다. 이 데코레이터가 적용된 메서드 내부에서 발생하는 모든 리포지토리 호출은 동일한 Prisma 트랜잭션을 공유합니다.
|
|
62
61
|
|
|
63
62
|
```typescript
|
|
64
63
|
import { Inject } from '@fluojs/core';
|
|
65
|
-
import { PrismaService } from '@fluojs/prisma';
|
|
64
|
+
import { PrismaService, Transaction, type PrismaServiceFacade } from '@fluojs/prisma';
|
|
66
65
|
import { PrismaClient } from '@prisma/client';
|
|
66
|
+
import { UserRepository } from './user.repository';
|
|
67
|
+
|
|
68
|
+
export class UserService {
|
|
69
|
+
constructor(private readonly repo: UserRepository) {}
|
|
70
|
+
|
|
71
|
+
@Transaction()
|
|
72
|
+
async onboardUser(dto: CreateUserDto) {
|
|
73
|
+
const user = await this.repo.create(dto);
|
|
74
|
+
await this.repo.initProfile(user.id);
|
|
75
|
+
return user;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
67
78
|
|
|
68
79
|
@Inject(PrismaService)
|
|
69
80
|
export class UserRepository {
|
|
70
|
-
constructor(private readonly prisma:
|
|
81
|
+
constructor(private readonly prisma: PrismaServiceFacade<PrismaClient>) {}
|
|
82
|
+
|
|
83
|
+
async create(data: any) {
|
|
84
|
+
// facade 타입은 표준 PrismaClient delegate를 노출합니다.
|
|
85
|
+
// @Transaction() 내부에서 호출되면 자동으로 활성 트랜잭션에 참여합니다.
|
|
86
|
+
return this.prisma.user.create({ data });
|
|
87
|
+
}
|
|
71
88
|
|
|
72
|
-
async
|
|
73
|
-
|
|
74
|
-
return this.prisma.current().user.findUnique({ where: { id } });
|
|
89
|
+
async initProfile(userId: string) {
|
|
90
|
+
return this.prisma.profile.create({ data: { userId } });
|
|
75
91
|
}
|
|
76
92
|
}
|
|
77
93
|
```
|
|
78
94
|
|
|
95
|
+
`@Transaction()` 메서드 호출은 재진입(reentrant)이 가능합니다. 데코레이터가 적용된 메서드가 다른 데코레이터 적용 메서드를 호출하더라도 하나의 동일한 Prisma 트랜잭션 안에서 실행됩니다.
|
|
96
|
+
|
|
79
97
|
### 여러 클라이언트를 위한 이름 있는 등록
|
|
80
98
|
|
|
81
|
-
하나의 애플리케이션 컨테이너 안에서 여러 Prisma Client가 필요하다면 각 등록에 명시적인 `name`을 부여하고 `getPrismaServiceToken(name)`으로 대응되는 토큰을 주입하세요.
|
|
99
|
+
하나의 애플리케이션 컨테이너 안에서 여러 Prisma Client가 필요하다면 각 등록에 명시적인 `name`을 부여하고 `getPrismaServiceToken(name)`으로 대응되는 토큰을 주입하세요. 이름 있는 클라이언트를 사용할 때는 `@Transaction()`에 해당 서비스로 접근할 수 있는 accessor를 전달하세요.
|
|
82
100
|
|
|
83
101
|
```typescript
|
|
84
102
|
import { Inject } from '@fluojs/core';
|
|
85
|
-
import { PrismaModule, PrismaService, getPrismaServiceToken } from '@fluojs/prisma';
|
|
103
|
+
import { PrismaModule, PrismaService, getPrismaServiceToken, Transaction, type PrismaServiceFacade } from '@fluojs/prisma';
|
|
86
104
|
|
|
87
105
|
const usersPrismaModule = PrismaModule.forRoot({ name: 'users', client: usersPrisma });
|
|
88
106
|
const analyticsPrismaModule = PrismaModule.forRoot({ name: 'analytics', client: analyticsPrisma });
|
|
@@ -90,55 +108,57 @@ const analyticsPrismaModule = PrismaModule.forRoot({ name: 'analytics', client:
|
|
|
90
108
|
@Inject(getPrismaServiceToken('users'), getPrismaServiceToken('analytics'))
|
|
91
109
|
export class MultiDatabaseService {
|
|
92
110
|
constructor(
|
|
93
|
-
private readonly users:
|
|
94
|
-
private readonly analytics:
|
|
111
|
+
private readonly users: PrismaServiceFacade<typeof usersPrisma>,
|
|
112
|
+
private readonly analytics: PrismaServiceFacade<typeof analyticsPrisma>,
|
|
95
113
|
) {}
|
|
96
114
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
115
|
+
@Transaction((self) => self.users)
|
|
116
|
+
async updateAndLog(userId: string, data: any) {
|
|
117
|
+
const user = await this.users.user.update({ where: { id: userId }, data });
|
|
118
|
+
// 이 호출은 'analytics'가 별도로 트랜잭션을 열지 않는 한 'users' 트랜잭션 밖에 있습니다.
|
|
119
|
+
await this.analytics.report.create({ data: { event: 'update', userId } });
|
|
120
|
+
return user;
|
|
101
121
|
}
|
|
102
122
|
}
|
|
103
123
|
```
|
|
104
124
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
### 수동 트랜잭션
|
|
125
|
+
### 수동 트랜잭션과 current()
|
|
108
126
|
|
|
109
|
-
`
|
|
127
|
+
`PrismaService`는 트랜잭션 범위 내에 있으면 자동으로 트랜잭션용 클라이언트를, 그렇지 않으면 루트 클라이언트를 반환하는 `current()` 메서드를 제공합니다. 외부 라이브러리에 클라이언트를 전달하거나 복잡한 수동 트랜잭션 처리가 필요한 경우 escape hatch로 사용하세요.
|
|
110
128
|
|
|
111
129
|
```typescript
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
});
|
|
116
|
-
```
|
|
130
|
+
import { Inject } from '@fluojs/core';
|
|
131
|
+
import { PrismaService } from '@fluojs/prisma';
|
|
132
|
+
import { PrismaClient } from '@prisma/client';
|
|
117
133
|
|
|
118
|
-
|
|
134
|
+
@Inject(PrismaService)
|
|
135
|
+
export class AdvancedRepository {
|
|
136
|
+
constructor(private readonly prisma: PrismaService<PrismaClient>) {}
|
|
119
137
|
|
|
120
|
-
|
|
138
|
+
async customOperation() {
|
|
139
|
+
const tx = this.prisma.current();
|
|
140
|
+
// fluo가 자동으로 감싸지 않는 작업을 수행하거나,
|
|
141
|
+
// PrismaClient를 직접 기대하는 외부 유틸리티에 전달할 때 tx를 사용하세요.
|
|
142
|
+
return tx.user.findMany();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
121
146
|
|
|
122
|
-
|
|
147
|
+
수동 대화형 트랜잭션 블록에는 `prisma.transaction()`을 사용하세요:
|
|
123
148
|
|
|
124
149
|
```typescript
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@Post()
|
|
131
|
-
async create() {
|
|
132
|
-
// 이후 PrismaService.current()를 사용하는 모든 리포지토리 호출은 이 트랜잭션을 공유합니다.
|
|
133
|
-
}
|
|
134
|
-
}
|
|
150
|
+
await this.prisma.transaction(async () => {
|
|
151
|
+
const tx = this.prisma.current();
|
|
152
|
+
const user = await tx.user.create({ data });
|
|
153
|
+
await tx.profile.create({ data: { userId: user.id } });
|
|
154
|
+
});
|
|
135
155
|
```
|
|
136
156
|
|
|
137
|
-
|
|
157
|
+
이미 활성 트랜잭션 컨텍스트가 있는 상태에서 `transaction()`을 호출하면 `PrismaService`는 중첩 Prisma 트랜잭션을 새로 열지 않고 활성 트랜잭션 클라이언트를 재사용합니다. 중첩 호출에는 isolation level 같은 트랜잭션 옵션을 전달하면 안 됩니다. 활성 컨텍스트에서 옵션을 제공하면 ambient transaction을 재사용하는 동안 호출자의 의도를 조용히 버리지 않도록 예외로 거부합니다.
|
|
138
158
|
|
|
139
159
|
### 종료와 status 계약
|
|
140
160
|
|
|
141
|
-
`PrismaService.requestTransaction(...)`은 정상 serving 전과 중에는 사용할 수 있지만, 애플리케이션
|
|
161
|
+
`PrismaService.requestTransaction(...)`은 정상 serving 전과 중에는 사용할 수 있지만, 애플리케이션 shutdown이 시작된 뒤에는 새 요청 범위 트랜잭션을 거부합니다. 종료 중에는 열린 요청 트랜잭션을 abort하고, 가장 바깥 transaction boundary가 settle될 때까지 추적한 다음 `$disconnect()` 실행 전에 drain합니다. 기존 수동 `transaction(...)` boundary 안에서 열린 중첩 `requestTransaction(...)` 호출도 동일합니다. 해당 호출은 ambient Prisma transaction client를 재사용하고, 바깥 boundary가 끝날 때까지 `details.activeRequestTransactions`에 표시되며, 두 번째 Prisma transaction을 열지 않습니다.
|
|
142
162
|
|
|
143
163
|
`createPrismaPlatformStatusSnapshot(...)`와 `PrismaService.createPlatformStatusSnapshot()`은 같은 라이프사이클 계약을 진단 surface에 노출합니다.
|
|
144
164
|
|
|
@@ -175,7 +195,7 @@ PrismaModule.forRootAsync({
|
|
|
175
195
|
|
|
176
196
|
```typescript
|
|
177
197
|
import { defineModule } from '@fluojs/runtime';
|
|
178
|
-
import { PrismaModule
|
|
198
|
+
import { PrismaModule } from '@fluojs/prisma';
|
|
179
199
|
import { PrismaClient } from '@prisma/client';
|
|
180
200
|
|
|
181
201
|
const prisma = new PrismaClient();
|
|
@@ -183,7 +203,6 @@ const prisma = new PrismaClient();
|
|
|
183
203
|
class ManualPrismaModule {}
|
|
184
204
|
|
|
185
205
|
defineModule(ManualPrismaModule, {
|
|
186
|
-
exports: [PrismaService, PrismaTransactionInterceptor],
|
|
187
206
|
imports: [PrismaModule.forRoot({ client: prisma })],
|
|
188
207
|
});
|
|
189
208
|
```
|
|
@@ -210,9 +229,11 @@ defineModule(ManualPrismaModule, {
|
|
|
210
229
|
- `requestTransaction(fn, signal?, options?): Promise<T>`
|
|
211
230
|
- HTTP 요청 라이프사이클에 특화된 트랜잭션 경계를 실행합니다. Abort를 인식하고, shutdown 중에는 disconnect 전에 열린 요청 트랜잭션을 drain하며, Prisma client가 `signal` 옵션을 거부하면 해당 옵션 없이 재시도합니다. `transaction()`과 마찬가지로 중첩 호출은 활성 트랜잭션 컨텍스트를 재사용하고, 트랜잭션 설정을 조용히 무시하지 않도록 중첩 옵션을 거부합니다.
|
|
212
231
|
|
|
213
|
-
|
|
232
|
+
Provider가 `current()`, `transaction(...)`, `requestTransaction(...)`, `createPlatformStatusSnapshot()` 같은 wrapper 메서드만 필요로 한다면 `PrismaService<TClient>`를 사용하세요. 생성된 Prisma Client delegate를 직접 호출하는 repository 주입에는 `PrismaServiceFacade<TClient>`를 사용하세요. 이 facade는 활성 트랜잭션이 있으면 해당 트랜잭션 client로, 없으면 root client로 호출을 전달합니다. `PrismaService.createFacade(...)`는 module-provider wiring을 위한 저수준 compatibility helper로 유지되며, 애플리케이션 코드는 `PrismaModule.forRoot(...)` / `forRootAsync(...)`를 우선 사용해야 합니다.
|
|
233
|
+
|
|
234
|
+
### `Transaction`
|
|
214
235
|
|
|
215
|
-
-
|
|
236
|
+
- 서비스 계층 트랜잭션 경계를 위한 표준 TC39 method decorator입니다. 기본적으로 ambient `PrismaService`를 resolve하고, 이름 있는 client에는 accessor를 받을 수 있으며, 외부 경계에는 Prisma transaction option을 전달할 수 있습니다.
|
|
216
237
|
|
|
217
238
|
### `PRISMA_CLIENT` (Token)
|
|
218
239
|
|
|
@@ -241,6 +262,7 @@ defineModule(ManualPrismaModule, {
|
|
|
241
262
|
- `PrismaModuleOptions`
|
|
242
263
|
- `PrismaClientLike`
|
|
243
264
|
- `PrismaHandleProvider`
|
|
265
|
+
- `PrismaServiceFacade<TClient>`
|
|
244
266
|
- `PrismaTransactionClient<TClient>`
|
|
245
267
|
- `InferPrismaTransactionClient<TClient>`
|
|
246
268
|
- `InferPrismaTransactionOptions<TClient>`
|
|
@@ -248,7 +270,7 @@ defineModule(ManualPrismaModule, {
|
|
|
248
270
|
## 관련 패키지
|
|
249
271
|
|
|
250
272
|
- `@fluojs/runtime`: 애플리케이션 라이프사이클 훅을 관리합니다.
|
|
251
|
-
- `@fluojs/http`:
|
|
273
|
+
- `@fluojs/http`: 명시적 `requestTransaction(...)` 경계와 함께 사용할 수 있는 요청 라이프사이클 primitive를 제공합니다.
|
|
252
274
|
- `@fluojs/terminus`: Prisma를 위한 헬스 인디케이터를 제공합니다.
|
|
253
275
|
|
|
254
276
|
## 예제 소스
|
package/README.md
CHANGED
|
@@ -10,10 +10,9 @@ Prisma lifecycle and ALS-backed transaction context for fluo applications. Conne
|
|
|
10
10
|
- [When to Use](#when-to-use)
|
|
11
11
|
- [Quick Start](#quick-start)
|
|
12
12
|
- [Common Patterns](#common-patterns)
|
|
13
|
-
- [
|
|
13
|
+
- [Service Transaction Boundary (@Transaction)](#service-transaction-boundary-transaction)
|
|
14
14
|
- [Named Registrations for Multiple Clients](#named-registrations-for-multiple-clients)
|
|
15
|
-
- [Manual Transactions](#manual-transactions)
|
|
16
|
-
- [Automatic Request Transactions](#automatic-request-transactions)
|
|
15
|
+
- [Manual Transactions and current()](#manual-transactions-and-current)
|
|
17
16
|
- [Shutdown and Status Contracts](#shutdown-and-status-contracts)
|
|
18
17
|
- [Async Configuration and Isolation](#async-configuration-and-isolation)
|
|
19
18
|
- [Manual Module Composition](#manual-module-composition)
|
|
@@ -56,33 +55,52 @@ class AppModule {}
|
|
|
56
55
|
|
|
57
56
|
## Common Patterns
|
|
58
57
|
|
|
59
|
-
###
|
|
58
|
+
### Service Transaction Boundary (@Transaction)
|
|
60
59
|
|
|
61
|
-
The `
|
|
60
|
+
The `@Transaction()` decorator is the recommended way to define transaction boundaries in your service layer. It ensures that all repository calls made within the decorated method share the same Prisma transaction.
|
|
62
61
|
|
|
63
62
|
```typescript
|
|
64
63
|
import { Inject } from '@fluojs/core';
|
|
65
|
-
import { PrismaService } from '@fluojs/prisma';
|
|
64
|
+
import { PrismaService, Transaction, type PrismaServiceFacade } from '@fluojs/prisma';
|
|
66
65
|
import { PrismaClient } from '@prisma/client';
|
|
66
|
+
import { UserRepository } from './user.repository';
|
|
67
|
+
|
|
68
|
+
export class UserService {
|
|
69
|
+
constructor(private readonly repo: UserRepository) {}
|
|
70
|
+
|
|
71
|
+
@Transaction()
|
|
72
|
+
async onboardUser(dto: CreateUserDto) {
|
|
73
|
+
const user = await this.repo.create(dto);
|
|
74
|
+
await this.repo.initProfile(user.id);
|
|
75
|
+
return user;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
67
78
|
|
|
68
79
|
@Inject(PrismaService)
|
|
69
80
|
export class UserRepository {
|
|
70
|
-
constructor(private readonly prisma:
|
|
81
|
+
constructor(private readonly prisma: PrismaServiceFacade<PrismaClient>) {}
|
|
82
|
+
|
|
83
|
+
async create(data: any) {
|
|
84
|
+
// The facade type exposes standard PrismaClient delegates.
|
|
85
|
+
// When called inside @Transaction(), they automatically participate in the ambient transaction.
|
|
86
|
+
return this.prisma.user.create({ data });
|
|
87
|
+
}
|
|
71
88
|
|
|
72
|
-
async
|
|
73
|
-
|
|
74
|
-
return this.prisma.current().user.findUnique({ where: { id } });
|
|
89
|
+
async initProfile(userId: string) {
|
|
90
|
+
return this.prisma.profile.create({ data: { userId } });
|
|
75
91
|
}
|
|
76
92
|
}
|
|
77
93
|
```
|
|
78
94
|
|
|
95
|
+
Calls to `@Transaction()` methods are reentrant. If a decorated method calls another decorated method, they share the same underlying Prisma transaction.
|
|
96
|
+
|
|
79
97
|
### Named Registrations for Multiple Clients
|
|
80
98
|
|
|
81
|
-
When one application container needs more than one Prisma client, register each client with an explicit `name` and inject the matching token with `getPrismaServiceToken(name)`.
|
|
99
|
+
When one application container needs more than one Prisma client, register each client with an explicit `name` and inject the matching token with `getPrismaServiceToken(name)`. For named clients, pass an accessor to `@Transaction()` to target the correct service.
|
|
82
100
|
|
|
83
101
|
```typescript
|
|
84
102
|
import { Inject } from '@fluojs/core';
|
|
85
|
-
import { PrismaModule, PrismaService, getPrismaServiceToken } from '@fluojs/prisma';
|
|
103
|
+
import { PrismaModule, PrismaService, getPrismaServiceToken, Transaction, type PrismaServiceFacade } from '@fluojs/prisma';
|
|
86
104
|
|
|
87
105
|
const usersPrismaModule = PrismaModule.forRoot({ name: 'users', client: usersPrisma });
|
|
88
106
|
const analyticsPrismaModule = PrismaModule.forRoot({ name: 'analytics', client: analyticsPrisma });
|
|
@@ -90,51 +108,54 @@ const analyticsPrismaModule = PrismaModule.forRoot({ name: 'analytics', client:
|
|
|
90
108
|
@Inject(getPrismaServiceToken('users'), getPrismaServiceToken('analytics'))
|
|
91
109
|
export class MultiDatabaseService {
|
|
92
110
|
constructor(
|
|
93
|
-
private readonly users:
|
|
94
|
-
private readonly analytics:
|
|
111
|
+
private readonly users: PrismaServiceFacade<typeof usersPrisma>,
|
|
112
|
+
private readonly analytics: PrismaServiceFacade<typeof analyticsPrisma>,
|
|
95
113
|
) {}
|
|
96
114
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
115
|
+
@Transaction((self) => self.users)
|
|
116
|
+
async updateAndLog(userId: string, data: any) {
|
|
117
|
+
const user = await this.users.user.update({ where: { id: userId }, data });
|
|
118
|
+
// This call is outside the 'users' transaction unless 'analytics' also opens one
|
|
119
|
+
await this.analytics.report.create({ data: { event: 'update', userId } });
|
|
120
|
+
return user;
|
|
101
121
|
}
|
|
102
122
|
}
|
|
103
123
|
```
|
|
104
124
|
|
|
105
|
-
Unnamed registration remains the default single-client path for `PrismaService`, `PRISMA_CLIENT`, `PRISMA_OPTIONS`, and `PrismaTransactionInterceptor`. When you register multiple Prisma clients in the same container, use names for every additional client so token resolution stays explicit.
|
|
106
125
|
|
|
107
|
-
### Manual Transactions
|
|
126
|
+
### Manual Transactions and current()
|
|
108
127
|
|
|
109
|
-
|
|
128
|
+
The `PrismaService` provides a `current()` method that returns the active transaction client if inside a transaction scope, or the root client otherwise. Use this as an escape hatch when you need to pass the client to external libraries or perform advanced manual transaction plumbing.
|
|
110
129
|
|
|
111
130
|
```typescript
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
});
|
|
116
|
-
```
|
|
131
|
+
import { Inject } from '@fluojs/core';
|
|
132
|
+
import { PrismaService } from '@fluojs/prisma';
|
|
133
|
+
import { PrismaClient } from '@prisma/client';
|
|
117
134
|
|
|
118
|
-
|
|
135
|
+
@Inject(PrismaService)
|
|
136
|
+
export class AdvancedRepository {
|
|
137
|
+
constructor(private readonly prisma: PrismaService<PrismaClient>) {}
|
|
119
138
|
|
|
120
|
-
|
|
139
|
+
async customOperation() {
|
|
140
|
+
const tx = this.prisma.current();
|
|
141
|
+
// Use tx for operations that fluo doesn't automatically wrap,
|
|
142
|
+
// or when passing to an external utility that expects a PrismaClient.
|
|
143
|
+
return tx.user.findMany();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
121
147
|
|
|
122
|
-
|
|
148
|
+
Use `prisma.transaction()` for manual interactive transaction blocks:
|
|
123
149
|
|
|
124
150
|
```typescript
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@Post()
|
|
131
|
-
async create() {
|
|
132
|
-
// All downstream repository calls via PrismaService.current() share this tx
|
|
133
|
-
}
|
|
134
|
-
}
|
|
151
|
+
await this.prisma.transaction(async () => {
|
|
152
|
+
const tx = this.prisma.current();
|
|
153
|
+
const user = await tx.user.create({ data });
|
|
154
|
+
await tx.profile.create({ data: { userId: user.id } });
|
|
155
|
+
});
|
|
135
156
|
```
|
|
136
157
|
|
|
137
|
-
`
|
|
158
|
+
When `transaction()` is called while a transaction context is already active, `PrismaService` reuses the active transaction client instead of opening a nested Prisma transaction. Nested calls must not pass transaction options such as isolation levels; providing options in an active context is rejected so the package does not silently drop caller intent while reusing the ambient transaction.
|
|
138
159
|
|
|
139
160
|
### Shutdown and Status Contracts
|
|
140
161
|
|
|
@@ -175,7 +196,7 @@ Use `PrismaModule.forRoot(...)` / `forRootAsync(...)` to register Prisma. When y
|
|
|
175
196
|
|
|
176
197
|
```typescript
|
|
177
198
|
import { defineModule } from '@fluojs/runtime';
|
|
178
|
-
import { PrismaModule
|
|
199
|
+
import { PrismaModule } from '@fluojs/prisma';
|
|
179
200
|
import { PrismaClient } from '@prisma/client';
|
|
180
201
|
|
|
181
202
|
const prisma = new PrismaClient();
|
|
@@ -183,7 +204,6 @@ const prisma = new PrismaClient();
|
|
|
183
204
|
class ManualPrismaModule {}
|
|
184
205
|
|
|
185
206
|
defineModule(ManualPrismaModule, {
|
|
186
|
-
exports: [PrismaService, PrismaTransactionInterceptor],
|
|
187
207
|
imports: [PrismaModule.forRoot({ client: prisma })],
|
|
188
208
|
});
|
|
189
209
|
```
|
|
@@ -210,9 +230,11 @@ defineModule(ManualPrismaModule, {
|
|
|
210
230
|
- `requestTransaction(fn, signal?, options?): Promise<T>`
|
|
211
231
|
- Specialized transaction boundary for HTTP request lifecycles. It is abort-aware, drains during shutdown before disconnect, and retries without `signal` when a Prisma client rejects that option. Like `transaction()`, nested calls reuse the active transaction context and reject nested options to avoid silently ignoring transaction settings.
|
|
212
232
|
|
|
213
|
-
|
|
233
|
+
Use `PrismaService<TClient>` when a provider only needs wrapper methods such as `current()`, `transaction(...)`, `requestTransaction(...)`, or `createPlatformStatusSnapshot()`. Use `PrismaServiceFacade<TClient>` for repository injections that call generated Prisma Client delegates directly; the facade forwards those calls to the active transaction client when one exists and to the root client otherwise. `PrismaService.createFacade(...)` is retained as a low-level compatibility helper for module-provider wiring; application code should prefer `PrismaModule.forRoot(...)` / `forRootAsync(...)`.
|
|
234
|
+
|
|
235
|
+
### `Transaction`
|
|
214
236
|
|
|
215
|
-
-
|
|
237
|
+
- Standard TC39 method decorator for service-layer transaction boundaries. It resolves the ambient `PrismaService` by default, accepts an accessor for named clients, and can forward Prisma transaction options to the outer boundary.
|
|
216
238
|
|
|
217
239
|
### `PRISMA_CLIENT` (Token)
|
|
218
240
|
|
|
@@ -243,6 +265,7 @@ token are deliberately not exported.
|
|
|
243
265
|
- `PrismaModuleOptions`
|
|
244
266
|
- `PrismaClientLike`
|
|
245
267
|
- `PrismaHandleProvider`
|
|
268
|
+
- `PrismaServiceFacade<TClient>`
|
|
246
269
|
- `PrismaTransactionClient<TClient>`
|
|
247
270
|
- `InferPrismaTransactionClient<TClient>`
|
|
248
271
|
- `InferPrismaTransactionOptions<TClient>`
|
|
@@ -250,7 +273,7 @@ token are deliberately not exported.
|
|
|
250
273
|
## Related Packages
|
|
251
274
|
|
|
252
275
|
- `@fluojs/runtime`: Manages the application lifecycle hooks.
|
|
253
|
-
- `@fluojs/http`: Provides
|
|
276
|
+
- `@fluojs/http`: Provides request lifecycle primitives that can be paired with explicit `requestTransaction(...)` boundaries.
|
|
254
277
|
- `@fluojs/terminus`: Provides a health indicator for Prisma.
|
|
255
278
|
|
|
256
279
|
## Example Sources
|
package/dist/module.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { AsyncModuleOptions } from '@fluojs/core';
|
|
2
2
|
import { type ModuleType } from '@fluojs/runtime';
|
|
3
3
|
import type { InferPrismaTransactionClient, InferPrismaTransactionOptions, PrismaClientLike, PrismaModuleOptions } from './types.js';
|
|
4
4
|
type PrismaAsyncModuleOptions<TClient extends PrismaClientLike<TTransactionClient, TTransactionOptions>, TTransactionClient, TTransactionOptions> = AsyncModuleOptions<Omit<PrismaModuleOptions<TClient, TTransactionClient, TTransactionOptions>, 'global' | 'name'>> & {
|
|
@@ -13,7 +13,7 @@ export declare class PrismaModule {
|
|
|
13
13
|
* Registers Prisma providers from static options.
|
|
14
14
|
*
|
|
15
15
|
* @param options Prisma module options with client handle and strict transaction mode.
|
|
16
|
-
* @returns A module definition that exports `PrismaService` and
|
|
16
|
+
* @returns A module definition that exports `PrismaService` and related Prisma tokens.
|
|
17
17
|
*/
|
|
18
18
|
static forRoot<TClient extends PrismaClientLike<TTransactionClient, TTransactionOptions>, TTransactionClient = InferPrismaTransactionClient<TClient>, TTransactionOptions = InferPrismaTransactionOptions<TClient>>(options: PrismaModuleOptions<TClient, TTransactionClient, TTransactionOptions>): ModuleType;
|
|
19
19
|
/**
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAS,MAAM,cAAc,CAAC;AAE9D,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAQhE,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,gBAAgB,EAChB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAapB,KAAK,wBAAwB,CAC3B,OAAO,SAAS,gBAAgB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,EACzE,kBAAkB,EAClB,mBAAmB,IACjB,kBAAkB,CAAC,IAAI,CAAC,mBAAmB,CAAC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC,CAAC,GAAG;IACvH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAiMF;;GAEG;AACH,qBAAa,YAAY;IACvB;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CACZ,OAAO,SAAS,gBAAgB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,EACzE,kBAAkB,GAAG,4BAA4B,CAAC,OAAO,CAAC,EAC1D,mBAAmB,GAAG,6BAA6B,CAAC,OAAO,CAAC,EAE5D,OAAO,EAAE,mBAAmB,CAAC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,CAAC,GAC7E,UAAU;IAIb;;;;;OAKG;IACH,MAAM,CAAC,YAAY,CACjB,OAAO,SAAS,gBAAgB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,EACzE,kBAAkB,GAAG,4BAA4B,CAAC,OAAO,CAAC,EAC1D,mBAAmB,GAAG,6BAA6B,CAAC,OAAO,CAAC,EAE5D,OAAO,EAAE,wBAAwB,CAAC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,CAAC,GAClF,UAAU;CAGd"}
|
package/dist/module.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { Inject } from '@fluojs/core';
|
|
2
1
|
import { defineModule } from '@fluojs/runtime';
|
|
3
2
|
import { PrismaService } from './service.js';
|
|
4
3
|
import { getPrismaClientToken, getPrismaOptionsToken, getPrismaServiceToken } from './tokens.js';
|
|
5
|
-
import { PrismaTransactionInterceptor } from './transaction.js';
|
|
6
4
|
const PRISMA_NORMALIZED_OPTIONS = Symbol('fluo.prisma.normalized-options');
|
|
7
5
|
function isObjectLike(value) {
|
|
8
6
|
return typeof value === 'object' && value !== null || typeof value === 'function';
|
|
@@ -35,16 +33,12 @@ function normalizePrismaModuleOptions(options) {
|
|
|
35
33
|
strictTransactions: options.strictTransactions ?? false
|
|
36
34
|
};
|
|
37
35
|
}
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return [NamedPrismaService, {
|
|
45
|
-
provide: serviceToken,
|
|
46
|
-
useExisting: NamedPrismaService
|
|
47
|
-
}];
|
|
36
|
+
function createPrismaServiceProvider(provide, clientToken, optionsToken) {
|
|
37
|
+
return {
|
|
38
|
+
inject: [clientToken, optionsToken],
|
|
39
|
+
provide,
|
|
40
|
+
useFactory: (client, serviceOptions) => PrismaService.createFacade(client, serviceOptions)
|
|
41
|
+
};
|
|
48
42
|
}
|
|
49
43
|
function createPrismaRuntimeProviders(normalizedOptionsProvider, name) {
|
|
50
44
|
const normalizedOptionsToken = getPrismaNormalizedOptionsToken(name);
|
|
@@ -60,10 +54,10 @@ function createPrismaRuntimeProviders(normalizedOptionsProvider, name) {
|
|
|
60
54
|
useFactory: options => ({
|
|
61
55
|
strictTransactions: options.strictTransactions
|
|
62
56
|
})
|
|
63
|
-
}, ...(name === undefined ? [PrismaService, {
|
|
57
|
+
}, ...(name === undefined ? [createPrismaServiceProvider(PrismaService, clientToken, optionsToken), {
|
|
64
58
|
provide: getPrismaServiceToken(),
|
|
65
59
|
useExisting: PrismaService
|
|
66
|
-
}
|
|
60
|
+
}] : [createPrismaServiceProvider(getPrismaServiceToken(name), clientToken, optionsToken)])];
|
|
67
61
|
}
|
|
68
62
|
function buildPrismaModule(options) {
|
|
69
63
|
class PrismaRootModuleDefinition {}
|
|
@@ -72,7 +66,7 @@ function buildPrismaModule(options) {
|
|
|
72
66
|
throw new Error('Named Prisma registrations are scoped and cannot be registered globally.');
|
|
73
67
|
}
|
|
74
68
|
return defineModule(PrismaRootModuleDefinition, {
|
|
75
|
-
exports: normalizedOptions.name === undefined ? [PrismaService,
|
|
69
|
+
exports: normalizedOptions.name === undefined ? [PrismaService, getPrismaServiceToken(), getPrismaClientToken(), getPrismaOptionsToken()] : [getPrismaServiceToken(normalizedOptions.name), getPrismaClientToken(normalizedOptions.name), getPrismaOptionsToken(normalizedOptions.name)],
|
|
76
70
|
global: normalizedOptions.name === undefined ? normalizedOptions.global : false,
|
|
77
71
|
providers: createPrismaRuntimeProviders({
|
|
78
72
|
provide: getPrismaNormalizedOptionsToken(normalizedOptions.name),
|
|
@@ -101,7 +95,7 @@ function buildPrismaModuleAsync(options) {
|
|
|
101
95
|
}
|
|
102
96
|
};
|
|
103
97
|
return defineModule(PrismaAsyncModuleDefinition, {
|
|
104
|
-
exports: normalizedName === undefined ? [PrismaService,
|
|
98
|
+
exports: normalizedName === undefined ? [PrismaService, getPrismaServiceToken(), getPrismaClientToken(), getPrismaOptionsToken()] : [getPrismaServiceToken(normalizedName), getPrismaClientToken(normalizedName), getPrismaOptionsToken(normalizedName)],
|
|
105
99
|
global: normalizedName === undefined ? options.global ?? false : false,
|
|
106
100
|
providers: createPrismaRuntimeProviders(normalizedOptionsProvider, normalizedName)
|
|
107
101
|
});
|
|
@@ -115,7 +109,7 @@ export class PrismaModule {
|
|
|
115
109
|
* Registers Prisma providers from static options.
|
|
116
110
|
*
|
|
117
111
|
* @param options Prisma module options with client handle and strict transaction mode.
|
|
118
|
-
* @returns A module definition that exports `PrismaService` and
|
|
112
|
+
* @returns A module definition that exports `PrismaService` and related Prisma tokens.
|
|
119
113
|
*/
|
|
120
114
|
static forRoot(options) {
|
|
121
115
|
return buildPrismaModule(options);
|
package/dist/service.d.ts
CHANGED
|
@@ -18,6 +18,20 @@ export declare class PrismaService<TClient extends PrismaClientLike<TTransaction
|
|
|
18
18
|
private transactionAbortSignalSupport;
|
|
19
19
|
private lifecycleState;
|
|
20
20
|
constructor(client: TClient, serviceOptions?: PrismaServiceOptions);
|
|
21
|
+
/**
|
|
22
|
+
* Creates the low-level DI facade that forwards unknown Prisma API properties to the ambient `current()` client.
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* This compatibility helper is used by `PrismaModule` provider wiring. Application code should prefer
|
|
26
|
+
* `PrismaModule.forRoot(...)` or `PrismaModule.forRootAsync(...)`, then type injected repository handles as
|
|
27
|
+
* `PrismaServiceFacade<TClient>` when direct generated Prisma delegates are needed.
|
|
28
|
+
*
|
|
29
|
+
* @param client Root Prisma client registered in the module.
|
|
30
|
+
* @param serviceOptions Runtime transaction options consumed by the Fluo wrapper.
|
|
31
|
+
* @returns A transaction-aware facade that exposes wrapper methods plus the root Prisma client surface.
|
|
32
|
+
*/
|
|
33
|
+
static createFacade<TClient extends PrismaClientLike<TTransactionClient, TTransactionOptions>, TTransactionClient = InferPrismaTransactionClient<TClient>, TTransactionOptions = InferPrismaTransactionOptions<TClient>>(client: TClient, serviceOptions?: PrismaServiceOptions): PrismaServiceFacade<TClient, TTransactionClient, TTransactionOptions>;
|
|
34
|
+
private installCurrentClientFacade;
|
|
21
35
|
/**
|
|
22
36
|
* Returns the active Prisma handle for the current async context.
|
|
23
37
|
*
|
|
@@ -91,5 +105,18 @@ export declare class PrismaService<TClient extends PrismaClientLike<TTransaction
|
|
|
91
105
|
private trackActiveRequestTransaction;
|
|
92
106
|
private untrackActiveRequestTransaction;
|
|
93
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Injection-facing Prisma facade type that combines the Fluo wrapper methods with the registered Prisma client surface.
|
|
110
|
+
*
|
|
111
|
+
* @remarks
|
|
112
|
+
* `PrismaModule` resolves `PrismaService` to a facade that forwards unknown properties to `current()`. Use this type in
|
|
113
|
+
* repositories that call generated Prisma delegates directly, and use `PrismaService<TClient>` when only wrapper methods
|
|
114
|
+
* (`current()`, `transaction(...)`, `requestTransaction(...)`, and status snapshots) are needed.
|
|
115
|
+
*
|
|
116
|
+
* @typeParam TClient Root Prisma client shape registered in the module.
|
|
117
|
+
* @typeParam TTransactionClient Transaction-scoped client resolved inside `$transaction(...)` callbacks.
|
|
118
|
+
* @typeParam TTransactionOptions Options forwarded to Prisma interactive transactions.
|
|
119
|
+
*/
|
|
120
|
+
export type PrismaServiceFacade<TClient extends PrismaClientLike<TTransactionClient, TTransactionOptions>, TTransactionClient = InferPrismaTransactionClient<TClient>, TTransactionOptions = InferPrismaTransactionOptions<TClient>> = PrismaService<TClient, TTransactionClient, TTransactionOptions> & Omit<TClient, keyof PrismaService<TClient, TTransactionClient, TTransactionOptions>>;
|
|
94
121
|
export {};
|
|
95
122
|
//# sourceMappingURL=service.d.ts.map
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAK3E,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAQpB,UAAU,oBAAoB;IAC5B,kBAAkB,EAAE,OAAO,CAAC;CAC7B;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAK3E,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAQpB,UAAU,oBAAoB;IAC5B,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AA6GD;;;;;;GAMG;AACH,qBACa,aAAa,CACxB,OAAO,SAAS,gBAAgB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,EACzE,kBAAkB,GAAG,4BAA4B,CAAC,OAAO,CAAC,EAC1D,mBAAmB,GAAG,6BAA6B,CAAC,OAAO,CAAC,CAE5D,YAAW,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,CAAC,EAAE,YAAY,EAAE,qBAAqB;IAQpH,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAPjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAuD;IACpF,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAuC;IACjF,OAAO,CAAC,6BAA6B,CAA4C;IACjF,OAAO,CAAC,cAAc,CAAgE;gBAGnE,MAAM,EAAE,OAAO,EACf,cAAc,GAAE,oBAAoD;IAKvF;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,YAAY,CACjB,OAAO,SAAS,gBAAgB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,EACzE,kBAAkB,GAAG,4BAA4B,CAAC,OAAO,CAAC,EAC1D,mBAAmB,GAAG,6BAA6B,CAAC,OAAO,CAAC,EAE5D,MAAM,EAAE,OAAO,EACf,cAAc,GAAE,oBAAoD,GACnE,mBAAmB,CAAC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,CAAC;IAMxE,OAAO,CAAC,0BAA0B;IAkBlC;;;;;;;;;OASG;IACH,OAAO,IAAI,OAAO,GAAG,kBAAkB;YAIzB,wBAAwB;IAyChC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ7B,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB5C;;;;OAIG;IACH,4BAA4B;IAa5B;;;;;;;;;;;;;;;;;OAiBG;IACG,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;IAQrF;;;;;;;;;;;;;;;;;;OAkBG;IACG,kBAAkB,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC;YAkCpG,+BAA+B;YAyB/B,2BAA2B;IAqCzC,OAAO,CAAC,kCAAkC;IAM1C,OAAO,CAAC,iCAAiC;IAMzC,OAAO,CAAC,qBAAqB;IAM7B,OAAO,CAAC,oCAAoC;IAY5C,OAAO,CAAC,sCAAsC;YAYhC,qCAAqC;IAyBnD,OAAO,CAAC,6BAA6B;IAUrC,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,0BAA0B;IAWlC,OAAO,CAAC,6BAA6B;IAIrC,OAAO,CAAC,+BAA+B;CAGxC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,mBAAmB,CAC7B,OAAO,SAAS,gBAAgB,CAAC,kBAAkB,EAAE,mBAAmB,CAAC,EACzE,kBAAkB,GAAG,4BAA4B,CAAC,OAAO,CAAC,EAC1D,mBAAmB,GAAG,6BAA6B,CAAC,OAAO,CAAC,IAC1D,aAAa,CAAC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,CAAC,GACjE,IAAI,CAAC,OAAO,EAAE,MAAM,aAAa,CAAC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,CAAC,CAAC,CAAC"}
|
package/dist/service.js
CHANGED
|
@@ -11,6 +11,18 @@ import { PRISMA_CLIENT, PRISMA_OPTIONS } from './tokens.js';
|
|
|
11
11
|
const NESTED_TRANSACTION_OPTIONS_NOT_SUPPORTED_ERROR = 'Nested Prisma transaction options are not supported because the active transaction context is reused.';
|
|
12
12
|
const REQUEST_TRANSACTION_UNAVAILABLE_ERROR = 'Prisma request transactions are not available during shutdown.';
|
|
13
13
|
const TRANSACTION_CONTEXT_UNAVAILABLE_ERROR = 'Prisma transaction context requires AsyncLocalStorage support from the host runtime.';
|
|
14
|
+
function createCurrentClientPrismaFacade(target) {
|
|
15
|
+
return new Proxy(target, {
|
|
16
|
+
get(service, prop, receiver) {
|
|
17
|
+
if (prop in service) {
|
|
18
|
+
return Reflect.get(service, prop, receiver);
|
|
19
|
+
}
|
|
20
|
+
const currentClient = service.current();
|
|
21
|
+
const value = Reflect.get(currentClient, prop, currentClient);
|
|
22
|
+
return typeof value === 'function' ? value.bind(currentClient) : value;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
14
26
|
class AsyncLocalStorageTransactionContextStore {
|
|
15
27
|
kind = 'als';
|
|
16
28
|
storage;
|
|
@@ -68,6 +80,40 @@ class PrismaService {
|
|
|
68
80
|
}) {
|
|
69
81
|
this.client = client;
|
|
70
82
|
this.serviceOptions = serviceOptions;
|
|
83
|
+
this.installCurrentClientFacade();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Creates the low-level DI facade that forwards unknown Prisma API properties to the ambient `current()` client.
|
|
88
|
+
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* This compatibility helper is used by `PrismaModule` provider wiring. Application code should prefer
|
|
91
|
+
* `PrismaModule.forRoot(...)` or `PrismaModule.forRootAsync(...)`, then type injected repository handles as
|
|
92
|
+
* `PrismaServiceFacade<TClient>` when direct generated Prisma delegates are needed.
|
|
93
|
+
*
|
|
94
|
+
* @param client Root Prisma client registered in the module.
|
|
95
|
+
* @param serviceOptions Runtime transaction options consumed by the Fluo wrapper.
|
|
96
|
+
* @returns A transaction-aware facade that exposes wrapper methods plus the root Prisma client surface.
|
|
97
|
+
*/
|
|
98
|
+
static createFacade(client, serviceOptions = {
|
|
99
|
+
strictTransactions: false
|
|
100
|
+
}) {
|
|
101
|
+
return createCurrentClientPrismaFacade(new _PrismaService(client, serviceOptions));
|
|
102
|
+
}
|
|
103
|
+
installCurrentClientFacade() {
|
|
104
|
+
for (const prop of Reflect.ownKeys(this.client)) {
|
|
105
|
+
if (prop in this) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
Object.defineProperty(this, prop, {
|
|
109
|
+
configurable: true,
|
|
110
|
+
get: () => {
|
|
111
|
+
const current = this.current();
|
|
112
|
+
const value = Reflect.get(current, prop);
|
|
113
|
+
return typeof value === 'function' ? value.bind(current) : value;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
71
117
|
}
|
|
72
118
|
|
|
73
119
|
/**
|
|
@@ -326,4 +372,17 @@ class PrismaService {
|
|
|
326
372
|
_initClass();
|
|
327
373
|
}
|
|
328
374
|
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Injection-facing Prisma facade type that combines the Fluo wrapper methods with the registered Prisma client surface.
|
|
378
|
+
*
|
|
379
|
+
* @remarks
|
|
380
|
+
* `PrismaModule` resolves `PrismaService` to a facade that forwards unknown properties to `current()`. Use this type in
|
|
381
|
+
* repositories that call generated Prisma delegates directly, and use `PrismaService<TClient>` when only wrapper methods
|
|
382
|
+
* (`current()`, `transaction(...)`, `requestTransaction(...)`, and status snapshots) are needed.
|
|
383
|
+
*
|
|
384
|
+
* @typeParam TClient Root Prisma client shape registered in the module.
|
|
385
|
+
* @typeParam TTransactionClient Transaction-scoped client resolved inside `$transaction(...)` callbacks.
|
|
386
|
+
* @typeParam TTransactionOptions Options forwarded to Prisma interactive transactions.
|
|
387
|
+
*/
|
|
329
388
|
export { _PrismaService as PrismaService };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TC39 standard method decorator — 2023-11 (Babel @babel/plugin-proposal-decorators)
|
|
3
|
+
* NO reflect-metadata. NO experimentalDecorators.
|
|
4
|
+
*
|
|
5
|
+
* Factory signature:
|
|
6
|
+
* Transaction(accessor?) returns (value, context: ClassMethodDecoratorContext) => Function
|
|
7
|
+
*
|
|
8
|
+
* Default: @Transaction() — uses `this` as the service
|
|
9
|
+
* Explicit: @Transaction((self) => self.analytics) — uses returned service property
|
|
10
|
+
*
|
|
11
|
+
* Nested reuse: if active context exists, reuses it (no new transaction opened)
|
|
12
|
+
* Nested options: if active context exists AND options passed, THROWS
|
|
13
|
+
*/
|
|
14
|
+
export type TransactionAccessor<THost, TService> = (self: THost) => TService;
|
|
15
|
+
/**
|
|
16
|
+
* Standard method decorator factory signature for Prisma service transaction boundaries.
|
|
17
|
+
*/
|
|
18
|
+
export type TransactionDecorator = <TService>(accessor?: TransactionAccessor<unknown, TService>) => (value: Function, context: ClassMethodDecoratorContext) => Function;
|
|
19
|
+
//# sourceMappingURL=transaction-decorator-contract.notes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction-decorator-contract.notes.d.ts","sourceRoot":"","sources":["../src/transaction-decorator-contract.notes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,mBAAmB,CAAC,KAAK,EAAE,QAAQ,IAAI,CAAC,IAAI,EAAE,KAAK,KAAK,QAAQ,CAAC;AAE7E;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,QAAQ,EAC1C,QAAQ,CAAC,EAAE,mBAAmB,CAAC,OAAO,EAAE,QAAQ,CAAC,KAC9C,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,2BAA2B,KAAK,QAAQ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/transaction.d.ts
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
type TransactionalPrismaService<TOptions = unknown> = {
|
|
2
|
+
transaction<T>(fn: () => Promise<T>, options?: TOptions): Promise<T>;
|
|
3
|
+
};
|
|
4
|
+
type TransactionAccessor<THost, TOptions> = (self: THost) => TransactionalPrismaService<TOptions>;
|
|
5
|
+
type TransactionMethod<THost, TArgs extends unknown[], TResult> = (this: THost, ...args: TArgs) => Promise<TResult>;
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
7
|
+
* Wraps a service method in a `PrismaService.transaction(...)` boundary.
|
|
6
8
|
*
|
|
7
9
|
* @remarks
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
+
* This is a TC39 standard method decorator (2023-11) and does not use legacy decorator metadata or `reflect-metadata`.
|
|
11
|
+
* `@Transaction()` resolves a Prisma service from the decorated instance, while
|
|
12
|
+
* `@Transaction((self) => self.prisma)` selects an explicit service for named or multi-client registrations.
|
|
13
|
+
* Calls made while a transaction context is already active reuse the existing Prisma transaction through `PrismaService`.
|
|
14
|
+
* Passing Prisma transaction options to a nested call is rejected by `PrismaService.transaction(...)` so option intent is not
|
|
15
|
+
* silently ignored.
|
|
16
|
+
*
|
|
17
|
+
* @param input Optional service accessor or Prisma interactive transaction options.
|
|
18
|
+
* @returns A standard method decorator that runs the original method inside a Prisma transaction boundary.
|
|
10
19
|
*/
|
|
11
|
-
export declare
|
|
12
|
-
|
|
13
|
-
constructor(prisma: PrismaService<PrismaClientLike>);
|
|
14
|
-
/**
|
|
15
|
-
* Runs the downstream handler inside a Prisma request transaction boundary.
|
|
16
|
-
*
|
|
17
|
-
* @param context Interceptor context that supplies the request abort signal.
|
|
18
|
-
* @param next Downstream handler chain.
|
|
19
|
-
* @returns The downstream handler result after the request transaction settles.
|
|
20
|
-
*/
|
|
21
|
-
intercept(context: InterceptorContext, next: {
|
|
22
|
-
handle(): Promise<unknown>;
|
|
23
|
-
}): Promise<unknown>;
|
|
24
|
-
}
|
|
20
|
+
export declare function Transaction<THost, TOptions = unknown>(input?: TransactionAccessor<THost, TOptions> | TOptions): <TArgs extends unknown[], TResult>(value: TransactionMethod<THost, TArgs, TResult>, context: ClassMethodDecoratorContext<THost, TransactionMethod<THost, TArgs, TResult>>) => TransactionMethod<THost, TArgs, TResult>;
|
|
21
|
+
export {};
|
|
25
22
|
//# sourceMappingURL=transaction.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"AAAA,KAAK,0BAA0B,CAAC,QAAQ,GAAG,OAAO,IAAI;IACpD,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACtE,CAAC;AAEF,KAAK,mBAAmB,CAAC,KAAK,EAAE,QAAQ,IAAI,CAAC,IAAI,EAAE,KAAK,KAAK,0BAA0B,CAAC,QAAQ,CAAC,CAAC;AAElG,KAAK,iBAAiB,CAAC,KAAK,EAAE,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,IAAI,CAChE,IAAI,EAAE,KAAK,EACX,GAAG,IAAI,EAAE,KAAK,KACX,OAAO,CAAC,OAAO,CAAC,CAAC;AAwDtB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,EACnD,KAAK,CAAC,EAAE,mBAAmB,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,GACtD,CAAC,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,EAClC,KAAK,EAAE,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,EAC/C,OAAO,EAAE,2BAA2B,CAAC,KAAK,EAAE,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,KAClF,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAiB5C"}
|
package/dist/transaction.js
CHANGED
|
@@ -1,39 +1,68 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { Inject } from '@fluojs/core';
|
|
8
|
-
import { PrismaService } from './service.js';
|
|
9
|
-
let _PrismaTransactionInt;
|
|
10
|
-
/**
|
|
11
|
-
* HTTP interceptor that wraps a request handler in `PrismaService.requestTransaction(...)`.
|
|
12
|
-
*
|
|
13
|
-
* @remarks
|
|
14
|
-
* Pair this with repository/service code that reads `PrismaService.current()` so downstream calls share the same
|
|
15
|
-
* request-scoped transaction client.
|
|
16
|
-
*/
|
|
17
|
-
class PrismaTransactionInterceptor {
|
|
18
|
-
static {
|
|
19
|
-
[_PrismaTransactionInt, _initClass] = _applyDecs(this, [Inject(PrismaService)], []).c;
|
|
1
|
+
function hasTransaction(value) {
|
|
2
|
+
return typeof value === 'object' && value !== null && 'transaction' in value && typeof value.transaction === 'function';
|
|
3
|
+
}
|
|
4
|
+
function readProperty(value, property) {
|
|
5
|
+
if (typeof value !== 'object' && typeof value !== 'function' || value === null) {
|
|
6
|
+
return undefined;
|
|
20
7
|
}
|
|
21
|
-
|
|
22
|
-
|
|
8
|
+
return Reflect.get(value, property);
|
|
9
|
+
}
|
|
10
|
+
function resolveDefaultPrismaService(self) {
|
|
11
|
+
const directPrisma = readProperty(self, 'prisma');
|
|
12
|
+
if (hasTransaction(directPrisma)) {
|
|
13
|
+
return directPrisma;
|
|
23
14
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* Runs the downstream handler inside a Prisma request transaction boundary.
|
|
27
|
-
*
|
|
28
|
-
* @param context Interceptor context that supplies the request abort signal.
|
|
29
|
-
* @param next Downstream handler chain.
|
|
30
|
-
* @returns The downstream handler result after the request transaction settles.
|
|
31
|
-
*/
|
|
32
|
-
async intercept(context, next) {
|
|
33
|
-
return this.prisma.requestTransaction(async () => next.handle(), context.requestContext.request.signal);
|
|
15
|
+
if (hasTransaction(self)) {
|
|
16
|
+
return self;
|
|
34
17
|
}
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
for (const value of Object.values(Object(self))) {
|
|
19
|
+
if (hasTransaction(value)) {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
const nestedPrisma = readProperty(value, 'prisma');
|
|
23
|
+
if (hasTransaction(nestedPrisma)) {
|
|
24
|
+
return nestedPrisma;
|
|
25
|
+
}
|
|
37
26
|
}
|
|
27
|
+
throw new Error('Unable to resolve PrismaService for @Transaction(). Provide an accessor function.');
|
|
38
28
|
}
|
|
39
|
-
|
|
29
|
+
function resolveTransactionInput(input) {
|
|
30
|
+
if (typeof input === 'function') {
|
|
31
|
+
return {
|
|
32
|
+
accessor: input
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
options: input
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Wraps a service method in a `PrismaService.transaction(...)` boundary.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* This is a TC39 standard method decorator (2023-11) and does not use legacy decorator metadata or `reflect-metadata`.
|
|
45
|
+
* `@Transaction()` resolves a Prisma service from the decorated instance, while
|
|
46
|
+
* `@Transaction((self) => self.prisma)` selects an explicit service for named or multi-client registrations.
|
|
47
|
+
* Calls made while a transaction context is already active reuse the existing Prisma transaction through `PrismaService`.
|
|
48
|
+
* Passing Prisma transaction options to a nested call is rejected by `PrismaService.transaction(...)` so option intent is not
|
|
49
|
+
* silently ignored.
|
|
50
|
+
*
|
|
51
|
+
* @param input Optional service accessor or Prisma interactive transaction options.
|
|
52
|
+
* @returns A standard method decorator that runs the original method inside a Prisma transaction boundary.
|
|
53
|
+
*/
|
|
54
|
+
export function Transaction(input) {
|
|
55
|
+
const {
|
|
56
|
+
accessor,
|
|
57
|
+
options
|
|
58
|
+
} = resolveTransactionInput(input);
|
|
59
|
+
return function transactionDecorator(value, context) {
|
|
60
|
+
if (context.kind !== 'method') {
|
|
61
|
+
throw new Error('@Transaction() can only decorate methods.');
|
|
62
|
+
}
|
|
63
|
+
return async function wrappedTransactionMethod(...args) {
|
|
64
|
+
const prisma = accessor?.(this) ?? resolveDefaultPrismaService(this);
|
|
65
|
+
return prisma.transaction(() => value.apply(this, args), options);
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"transaction",
|
|
10
10
|
"als"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0
|
|
12
|
+
"version": "1.1.0",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@fluojs/core": "^1.0.3",
|
|
40
|
-
"@fluojs/http": "^1.1.
|
|
41
|
-
"@fluojs/di": "^1.0
|
|
42
|
-
"@fluojs/runtime": "^1.1.
|
|
40
|
+
"@fluojs/http": "^1.1.2",
|
|
41
|
+
"@fluojs/di": "^1.1.0",
|
|
42
|
+
"@fluojs/runtime": "^1.1.8"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"@prisma/client": ">=5.0.0"
|