@fluojs/prisma 1.0.1 → 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 +76 -47
- package/README.md +74 -44
- package/dist/module.d.ts +2 -2
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +20 -17
- package/dist/service.d.ts +28 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +107 -4
- package/dist/status.d.ts +1 -0
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +9 -1
- 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 +5 -5
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
|
|
|
@@ -167,13 +187,15 @@ PrismaModule.forRootAsync({
|
|
|
167
187
|
|
|
168
188
|
하나의 컴파일된 애플리케이션 안에서는 하위 provider가 동일하게 resolve된 `PrismaService`, ALS 트랜잭션 컨텍스트, 라이프사이클 관리 대상 클라이언트를 공유합니다. 서로 다른 애플리케이션 컨테이너는 독립된 factory 결과를 받으므로 `$connect` / `$disconnect` 소유권과 요청 트랜잭션 상태가 격리됩니다.
|
|
169
189
|
|
|
190
|
+
트랜잭션 경계에는 호스트가 제공하는 `AsyncLocalStorage` 지원이 필요합니다. `@fluojs/prisma`는 런타임이 노출하는 `globalThis.AsyncLocalStorage` 또는 Node.js의 `process.getBuiltinModule('node:async_hooks')` 호스트 경계를 통해 이를 resolve합니다. 두 경로 모두 사용할 수 없으면 동기 stack fallback으로 async boundary 사이의 `current()`를 잃는 대신, Prisma 트랜잭션을 열기 전에 `transaction()`과 `requestTransaction()`이 예외를 던집니다. 이 상태는 `createPlatformStatusSnapshot().details.transactionContext`에 `unavailable`로 보고됩니다.
|
|
191
|
+
|
|
170
192
|
### 수동 모듈 조합
|
|
171
193
|
|
|
172
194
|
`PrismaModule.forRoot(...)` / `forRootAsync(...)`를 사용해 Prisma를 등록합니다. 커스텀 `defineModule(...)` 등록 안에서 Prisma 지원을 조합해야 할 때도 동일한 모듈 entrypoint를 import해서 사용하세요.
|
|
173
195
|
|
|
174
196
|
```typescript
|
|
175
197
|
import { defineModule } from '@fluojs/runtime';
|
|
176
|
-
import { PrismaModule
|
|
198
|
+
import { PrismaModule } from '@fluojs/prisma';
|
|
177
199
|
import { PrismaClient } from '@prisma/client';
|
|
178
200
|
|
|
179
201
|
const prisma = new PrismaClient();
|
|
@@ -181,7 +203,6 @@ const prisma = new PrismaClient();
|
|
|
181
203
|
class ManualPrismaModule {}
|
|
182
204
|
|
|
183
205
|
defineModule(ManualPrismaModule, {
|
|
184
|
-
exports: [PrismaService, PrismaTransactionInterceptor],
|
|
185
206
|
imports: [PrismaModule.forRoot({ client: prisma })],
|
|
186
207
|
});
|
|
187
208
|
```
|
|
@@ -196,7 +217,8 @@ defineModule(ManualPrismaModule, {
|
|
|
196
217
|
- `forRootAsync(...)`는 애플리케이션 컨테이너마다 옵션을 한 번 resolve하여, 별도 bootstrap 사이에서 클라이언트 라이프사이클과 요청 트랜잭션 격리를 보존합니다.
|
|
197
218
|
- `strictTransactions: true` 설정 시 트랜잭션 미지원 환경에서 즉시 예외를 발생시킵니다.
|
|
198
219
|
- `strictTransactions`가 `false`이면 클라이언트가 interactive `$transaction`을 제공하지 않을 때 직접 실행으로 fallback합니다.
|
|
199
|
-
-
|
|
220
|
+
- sync 및 async 등록 모두에서 `client`는 실제 object/function handle이어야 하며, 누락된 handle은 모듈 등록 또는 async bootstrap 중 거부됩니다.
|
|
221
|
+
- 이름 있는 등록의 `name`은 public token 생성 전에 trim되며, 빈 이름은 거부됩니다.
|
|
200
222
|
|
|
201
223
|
### `PrismaService<TClient>`
|
|
202
224
|
|
|
@@ -207,6 +229,12 @@ defineModule(ManualPrismaModule, {
|
|
|
207
229
|
- `requestTransaction(fn, signal?, options?): Promise<T>`
|
|
208
230
|
- HTTP 요청 라이프사이클에 특화된 트랜잭션 경계를 실행합니다. Abort를 인식하고, shutdown 중에는 disconnect 전에 열린 요청 트랜잭션을 drain하며, Prisma client가 `signal` 옵션을 거부하면 해당 옵션 없이 재시도합니다. `transaction()`과 마찬가지로 중첩 호출은 활성 트랜잭션 컨텍스트를 재사용하고, 트랜잭션 설정을 조용히 무시하지 않도록 중첩 옵션을 거부합니다.
|
|
209
231
|
|
|
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`
|
|
235
|
+
|
|
236
|
+
- 서비스 계층 트랜잭션 경계를 위한 표준 TC39 method decorator입니다. 기본적으로 ambient `PrismaService`를 resolve하고, 이름 있는 client에는 accessor를 받을 수 있으며, 외부 경계에는 Prisma transaction option을 전달할 수 있습니다.
|
|
237
|
+
|
|
210
238
|
### `PRISMA_CLIENT` (Token)
|
|
211
239
|
|
|
212
240
|
원시 `PrismaClient` 인스턴스를 위한 주입 토큰입니다.
|
|
@@ -234,6 +262,7 @@ defineModule(ManualPrismaModule, {
|
|
|
234
262
|
- `PrismaModuleOptions`
|
|
235
263
|
- `PrismaClientLike`
|
|
236
264
|
- `PrismaHandleProvider`
|
|
265
|
+
- `PrismaServiceFacade<TClient>`
|
|
237
266
|
- `PrismaTransactionClient<TClient>`
|
|
238
267
|
- `InferPrismaTransactionClient<TClient>`
|
|
239
268
|
- `InferPrismaTransactionOptions<TClient>`
|
|
@@ -241,7 +270,7 @@ defineModule(ManualPrismaModule, {
|
|
|
241
270
|
## 관련 패키지
|
|
242
271
|
|
|
243
272
|
- `@fluojs/runtime`: 애플리케이션 라이프사이클 훅을 관리합니다.
|
|
244
|
-
- `@fluojs/http`:
|
|
273
|
+
- `@fluojs/http`: 명시적 `requestTransaction(...)` 경계와 함께 사용할 수 있는 요청 라이프사이클 primitive를 제공합니다.
|
|
245
274
|
- `@fluojs/terminus`: Prisma를 위한 헬스 인디케이터를 제공합니다.
|
|
246
275
|
|
|
247
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
|
|
|
@@ -167,13 +188,15 @@ PrismaModule.forRootAsync({
|
|
|
167
188
|
|
|
168
189
|
Within one compiled application, downstream providers share the same resolved `PrismaService`, ALS transaction context, and lifecycle-managed client. Separate application containers receive independent factory results, so `$connect` / `$disconnect` ownership and request transaction state remain isolated.
|
|
169
190
|
|
|
191
|
+
Transaction boundaries require host-provided `AsyncLocalStorage` support. `@fluojs/prisma` resolves it through `globalThis.AsyncLocalStorage` when a runtime exposes one, or through the host's `process.getBuiltinModule('node:async_hooks')` boundary on Node.js. If neither path is available, `transaction()` and `requestTransaction()` reject before opening a Prisma transaction instead of using a synchronous stack fallback that would lose `current()` across async boundaries; `createPlatformStatusSnapshot().details.transactionContext` reports `unavailable` in that state.
|
|
192
|
+
|
|
170
193
|
### Manual Module Composition
|
|
171
194
|
|
|
172
195
|
Use `PrismaModule.forRoot(...)` / `forRootAsync(...)` to register Prisma. When you need to compose Prisma support inside a custom `defineModule(...)` registration, import the module entrypoint there as well.
|
|
173
196
|
|
|
174
197
|
```typescript
|
|
175
198
|
import { defineModule } from '@fluojs/runtime';
|
|
176
|
-
import { PrismaModule
|
|
199
|
+
import { PrismaModule } from '@fluojs/prisma';
|
|
177
200
|
import { PrismaClient } from '@prisma/client';
|
|
178
201
|
|
|
179
202
|
const prisma = new PrismaClient();
|
|
@@ -181,7 +204,6 @@ const prisma = new PrismaClient();
|
|
|
181
204
|
class ManualPrismaModule {}
|
|
182
205
|
|
|
183
206
|
defineModule(ManualPrismaModule, {
|
|
184
|
-
exports: [PrismaService, PrismaTransactionInterceptor],
|
|
185
207
|
imports: [PrismaModule.forRoot({ client: prisma })],
|
|
186
208
|
});
|
|
187
209
|
```
|
|
@@ -196,7 +218,8 @@ defineModule(ManualPrismaModule, {
|
|
|
196
218
|
- `forRootAsync(...)` resolves options once per application container, preserving client lifecycle and request transaction isolation across separate bootstraps.
|
|
197
219
|
- Supports `strictTransactions: true` to throw if transaction support is missing.
|
|
198
220
|
- When `strictTransactions` is `false`, PrismaService falls back to direct execution if the client does not expose interactive `$transaction`.
|
|
199
|
-
-
|
|
221
|
+
- `client` must be a concrete object/function handle for both sync and async registration; missing handles are rejected during module registration or async bootstrap.
|
|
222
|
+
- Names are trimmed for named registrations, and blank names are rejected before public tokens are created.
|
|
200
223
|
|
|
201
224
|
### `PrismaService<TClient>`
|
|
202
225
|
|
|
@@ -207,6 +230,12 @@ defineModule(ManualPrismaModule, {
|
|
|
207
230
|
- `requestTransaction(fn, signal?, options?): Promise<T>`
|
|
208
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.
|
|
209
232
|
|
|
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`
|
|
236
|
+
|
|
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.
|
|
238
|
+
|
|
210
239
|
### `PRISMA_CLIENT` (Token)
|
|
211
240
|
|
|
212
241
|
Injectable token for the raw `PrismaClient` instance.
|
|
@@ -236,6 +265,7 @@ token are deliberately not exported.
|
|
|
236
265
|
- `PrismaModuleOptions`
|
|
237
266
|
- `PrismaClientLike`
|
|
238
267
|
- `PrismaHandleProvider`
|
|
268
|
+
- `PrismaServiceFacade<TClient>`
|
|
239
269
|
- `PrismaTransactionClient<TClient>`
|
|
240
270
|
- `InferPrismaTransactionClient<TClient>`
|
|
241
271
|
- `InferPrismaTransactionOptions<TClient>`
|
|
@@ -243,7 +273,7 @@ token are deliberately not exported.
|
|
|
243
273
|
## Related Packages
|
|
244
274
|
|
|
245
275
|
- `@fluojs/runtime`: Manages the application lifecycle hooks.
|
|
246
|
-
- `@fluojs/http`: Provides
|
|
276
|
+
- `@fluojs/http`: Provides request lifecycle primitives that can be paired with explicit `requestTransaction(...)` boundaries.
|
|
247
277
|
- `@fluojs/terminus`: Provides a health indicator for Prisma.
|
|
248
278
|
|
|
249
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,13 +1,17 @@
|
|
|
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');
|
|
5
|
+
function isObjectLike(value) {
|
|
6
|
+
return typeof value === 'object' && value !== null || typeof value === 'function';
|
|
7
|
+
}
|
|
7
8
|
function normalizePrismaRegistrationName(name) {
|
|
8
9
|
if (name === undefined) {
|
|
9
10
|
return undefined;
|
|
10
11
|
}
|
|
12
|
+
if (typeof name !== 'string') {
|
|
13
|
+
throw new Error('PrismaModule name must be a string when provided.');
|
|
14
|
+
}
|
|
11
15
|
const normalizedName = name.trim();
|
|
12
16
|
if (normalizedName.length === 0) {
|
|
13
17
|
throw new Error('PrismaModule name must be a non-empty string when provided.');
|
|
@@ -19,6 +23,9 @@ function getPrismaNormalizedOptionsToken(name) {
|
|
|
19
23
|
return normalizedName === undefined ? PRISMA_NORMALIZED_OPTIONS : Symbol.for(`fluo.prisma.normalized-options:${normalizedName}`);
|
|
20
24
|
}
|
|
21
25
|
function normalizePrismaModuleOptions(options) {
|
|
26
|
+
if (!isObjectLike(options.client)) {
|
|
27
|
+
throw new Error('PrismaModule requires a client option.');
|
|
28
|
+
}
|
|
22
29
|
return {
|
|
23
30
|
name: normalizePrismaRegistrationName(options.name),
|
|
24
31
|
client: options.client,
|
|
@@ -26,16 +33,12 @@ function normalizePrismaModuleOptions(options) {
|
|
|
26
33
|
strictTransactions: options.strictTransactions ?? false
|
|
27
34
|
};
|
|
28
35
|
}
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return [NamedPrismaService, {
|
|
36
|
-
provide: serviceToken,
|
|
37
|
-
useExisting: NamedPrismaService
|
|
38
|
-
}];
|
|
36
|
+
function createPrismaServiceProvider(provide, clientToken, optionsToken) {
|
|
37
|
+
return {
|
|
38
|
+
inject: [clientToken, optionsToken],
|
|
39
|
+
provide,
|
|
40
|
+
useFactory: (client, serviceOptions) => PrismaService.createFacade(client, serviceOptions)
|
|
41
|
+
};
|
|
39
42
|
}
|
|
40
43
|
function createPrismaRuntimeProviders(normalizedOptionsProvider, name) {
|
|
41
44
|
const normalizedOptionsToken = getPrismaNormalizedOptionsToken(name);
|
|
@@ -51,10 +54,10 @@ function createPrismaRuntimeProviders(normalizedOptionsProvider, name) {
|
|
|
51
54
|
useFactory: options => ({
|
|
52
55
|
strictTransactions: options.strictTransactions
|
|
53
56
|
})
|
|
54
|
-
}, ...(name === undefined ? [PrismaService, {
|
|
57
|
+
}, ...(name === undefined ? [createPrismaServiceProvider(PrismaService, clientToken, optionsToken), {
|
|
55
58
|
provide: getPrismaServiceToken(),
|
|
56
59
|
useExisting: PrismaService
|
|
57
|
-
}
|
|
60
|
+
}] : [createPrismaServiceProvider(getPrismaServiceToken(name), clientToken, optionsToken)])];
|
|
58
61
|
}
|
|
59
62
|
function buildPrismaModule(options) {
|
|
60
63
|
class PrismaRootModuleDefinition {}
|
|
@@ -63,7 +66,7 @@ function buildPrismaModule(options) {
|
|
|
63
66
|
throw new Error('Named Prisma registrations are scoped and cannot be registered globally.');
|
|
64
67
|
}
|
|
65
68
|
return defineModule(PrismaRootModuleDefinition, {
|
|
66
|
-
exports: normalizedOptions.name === undefined ? [PrismaService,
|
|
69
|
+
exports: normalizedOptions.name === undefined ? [PrismaService, getPrismaServiceToken(), getPrismaClientToken(), getPrismaOptionsToken()] : [getPrismaServiceToken(normalizedOptions.name), getPrismaClientToken(normalizedOptions.name), getPrismaOptionsToken(normalizedOptions.name)],
|
|
67
70
|
global: normalizedOptions.name === undefined ? normalizedOptions.global : false,
|
|
68
71
|
providers: createPrismaRuntimeProviders({
|
|
69
72
|
provide: getPrismaNormalizedOptionsToken(normalizedOptions.name),
|
|
@@ -92,7 +95,7 @@ function buildPrismaModuleAsync(options) {
|
|
|
92
95
|
}
|
|
93
96
|
};
|
|
94
97
|
return defineModule(PrismaAsyncModuleDefinition, {
|
|
95
|
-
exports: normalizedName === undefined ? [PrismaService,
|
|
98
|
+
exports: normalizedName === undefined ? [PrismaService, getPrismaServiceToken(), getPrismaClientToken(), getPrismaOptionsToken()] : [getPrismaServiceToken(normalizedName), getPrismaClientToken(normalizedName), getPrismaOptionsToken(normalizedName)],
|
|
96
99
|
global: normalizedName === undefined ? options.global ?? false : false,
|
|
97
100
|
providers: createPrismaRuntimeProviders(normalizedOptionsProvider, normalizedName)
|
|
98
101
|
});
|
|
@@ -106,7 +109,7 @@ export class PrismaModule {
|
|
|
106
109
|
* Registers Prisma providers from static options.
|
|
107
110
|
*
|
|
108
111
|
* @param options Prisma module options with client handle and strict transaction mode.
|
|
109
|
-
* @returns A module definition that exports `PrismaService` and
|
|
112
|
+
* @returns A module definition that exports `PrismaService` and related Prisma tokens.
|
|
110
113
|
*/
|
|
111
114
|
static forRoot(options) {
|
|
112
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
|
*
|
|
@@ -80,6 +94,7 @@ export declare class PrismaService<TClient extends PrismaClientLike<TTransaction
|
|
|
80
94
|
private runWithRequestTransactionClient;
|
|
81
95
|
private runNestedRequestTransaction;
|
|
82
96
|
private assertRequestTransactionsAvailable;
|
|
97
|
+
private assertTransactionContextAvailable;
|
|
83
98
|
private throwIfRequestAborted;
|
|
84
99
|
private runRequestTransactionWithAbortSignal;
|
|
85
100
|
private canAttemptTransactionAbortSignalOption;
|
|
@@ -90,5 +105,18 @@ export declare class PrismaService<TClient extends PrismaClientLike<TTransaction
|
|
|
90
105
|
private trackActiveRequestTransaction;
|
|
91
106
|
private untrackActiveRequestTransaction;
|
|
92
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>>;
|
|
93
121
|
export {};
|
|
94
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":"
|
|
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
|
@@ -4,14 +4,61 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
|
|
|
4
4
|
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
5
5
|
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
|
|
6
6
|
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
|
|
7
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
8
7
|
import { createAbortError, createRequestAbortContext, raceWithAbort, trackActiveRequestTransaction, untrackActiveRequestTransaction } from '@fluojs/runtime';
|
|
9
8
|
import { Inject } from '@fluojs/core';
|
|
10
9
|
import { createPrismaPlatformStatusSnapshot } from './status.js';
|
|
11
10
|
import { PRISMA_CLIENT, PRISMA_OPTIONS } from './tokens.js';
|
|
12
11
|
const NESTED_TRANSACTION_OPTIONS_NOT_SUPPORTED_ERROR = 'Nested Prisma transaction options are not supported because the active transaction context is reused.';
|
|
13
12
|
const REQUEST_TRANSACTION_UNAVAILABLE_ERROR = 'Prisma request transactions are not available during shutdown.';
|
|
14
|
-
|
|
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
|
+
}
|
|
26
|
+
class AsyncLocalStorageTransactionContextStore {
|
|
27
|
+
kind = 'als';
|
|
28
|
+
storage;
|
|
29
|
+
constructor(AsyncLocalStorage) {
|
|
30
|
+
this.storage = new AsyncLocalStorage();
|
|
31
|
+
}
|
|
32
|
+
getStore() {
|
|
33
|
+
return this.storage.getStore();
|
|
34
|
+
}
|
|
35
|
+
run(context, callback) {
|
|
36
|
+
return this.storage.run(context, callback);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
class UnavailableTransactionContextStore {
|
|
40
|
+
kind = 'unavailable';
|
|
41
|
+
getStore() {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
run(_context, _callback) {
|
|
45
|
+
throw new Error(TRANSACTION_CONTEXT_UNAVAILABLE_ERROR);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function resolveAsyncLocalStorageConstructor(host = globalThis) {
|
|
49
|
+
if (typeof host.AsyncLocalStorage === 'function') {
|
|
50
|
+
return host.AsyncLocalStorage;
|
|
51
|
+
}
|
|
52
|
+
return host.process?.getBuiltinModule?.('node:async_hooks').AsyncLocalStorage;
|
|
53
|
+
}
|
|
54
|
+
function createTransactionContextStore() {
|
|
55
|
+
const AsyncLocalStorage = resolveAsyncLocalStorageConstructor();
|
|
56
|
+
if (typeof AsyncLocalStorage === 'function') {
|
|
57
|
+
return new AsyncLocalStorageTransactionContextStore(AsyncLocalStorage);
|
|
58
|
+
}
|
|
59
|
+
return new UnavailableTransactionContextStore();
|
|
60
|
+
}
|
|
61
|
+
|
|
15
62
|
/**
|
|
16
63
|
* Prisma runtime facade that owns lifecycle hooks and transaction context access.
|
|
17
64
|
*
|
|
@@ -19,11 +66,12 @@ let _PrismaService;
|
|
|
19
66
|
* @typeParam TTransactionClient Transaction-scoped client resolved inside `$transaction(...)` callbacks.
|
|
20
67
|
* @typeParam TTransactionOptions Options forwarded to Prisma interactive transactions.
|
|
21
68
|
*/
|
|
69
|
+
let _PrismaService;
|
|
22
70
|
class PrismaService {
|
|
23
71
|
static {
|
|
24
72
|
[_PrismaService, _initClass] = _applyDecs(this, [Inject(PRISMA_CLIENT, PRISMA_OPTIONS)], []).c;
|
|
25
73
|
}
|
|
26
|
-
transactions =
|
|
74
|
+
transactions = createTransactionContextStore();
|
|
27
75
|
activeRequestTransactions = new Set();
|
|
28
76
|
transactionAbortSignalSupport = 'unknown';
|
|
29
77
|
lifecycleState = 'created';
|
|
@@ -32,6 +80,40 @@ class PrismaService {
|
|
|
32
80
|
}) {
|
|
33
81
|
this.client = client;
|
|
34
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
|
+
}
|
|
35
117
|
}
|
|
36
118
|
|
|
37
119
|
/**
|
|
@@ -60,6 +142,7 @@ class PrismaService {
|
|
|
60
142
|
}
|
|
61
143
|
return fn();
|
|
62
144
|
}
|
|
145
|
+
this.assertTransactionContextAvailable();
|
|
63
146
|
const deferredRequestTransactionHandles = new Set();
|
|
64
147
|
try {
|
|
65
148
|
return await run(transactionClient => this.transactions.run({
|
|
@@ -103,7 +186,8 @@ class PrismaService {
|
|
|
103
186
|
supportsConnect: typeof this.client.$connect === 'function',
|
|
104
187
|
supportsDisconnect: typeof this.client.$disconnect === 'function',
|
|
105
188
|
supportsTransaction: typeof this.client.$transaction === 'function',
|
|
106
|
-
transactionAbortSignalSupport: this.transactionAbortSignalSupport
|
|
189
|
+
transactionAbortSignalSupport: this.transactionAbortSignalSupport,
|
|
190
|
+
transactionContext: this.transactions.kind
|
|
107
191
|
});
|
|
108
192
|
}
|
|
109
193
|
|
|
@@ -175,6 +259,7 @@ class PrismaService {
|
|
|
175
259
|
}
|
|
176
260
|
return fn();
|
|
177
261
|
}
|
|
262
|
+
this.assertTransactionContextAvailable();
|
|
178
263
|
return run(transactionClient => this.transactions.run({
|
|
179
264
|
client: transactionClient,
|
|
180
265
|
requestAbortSignal: signal
|
|
@@ -210,6 +295,11 @@ class PrismaService {
|
|
|
210
295
|
throw new Error(REQUEST_TRANSACTION_UNAVAILABLE_ERROR);
|
|
211
296
|
}
|
|
212
297
|
}
|
|
298
|
+
assertTransactionContextAvailable() {
|
|
299
|
+
if (this.transactions.kind === 'unavailable') {
|
|
300
|
+
throw new Error(TRANSACTION_CONTEXT_UNAVAILABLE_ERROR);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
213
303
|
throwIfRequestAborted(signal) {
|
|
214
304
|
if (signal.aborted) {
|
|
215
305
|
throw createAbortError(signal.reason);
|
|
@@ -282,4 +372,17 @@ class PrismaService {
|
|
|
282
372
|
_initClass();
|
|
283
373
|
}
|
|
284
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
|
+
*/
|
|
285
388
|
export { _PrismaService as PrismaService };
|
package/dist/status.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ type PrismaPlatformStatusSnapshotInput = {
|
|
|
8
8
|
supportsDisconnect: boolean;
|
|
9
9
|
supportsTransaction: boolean;
|
|
10
10
|
transactionAbortSignalSupport: 'unknown' | 'supported' | 'unsupported';
|
|
11
|
+
transactionContext?: 'als' | 'unavailable';
|
|
11
12
|
};
|
|
12
13
|
/**
|
|
13
14
|
* Create prisma platform status snapshot.
|
package/dist/status.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iCAAiC,EAGlC,MAAM,iBAAiB,CAAC;AAEzB,KAAK,4BAA4B,GAAG,SAAS,GAAG,OAAO,GAAG,eAAe,GAAG,SAAS,CAAC;AAEtF,KAAK,iCAAiC,GAAG;IACvC,yBAAyB,EAAE,MAAM,CAAC;IAClC,cAAc,EAAE,4BAA4B,CAAC;IAC7C,kBAAkB,EAAE,OAAO,CAAC;IAC5B,eAAe,EAAE,OAAO,CAAC;IACzB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,6BAA6B,EAAE,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iCAAiC,EAGlC,MAAM,iBAAiB,CAAC;AAEzB,KAAK,4BAA4B,GAAG,SAAS,GAAG,OAAO,GAAG,eAAe,GAAG,SAAS,CAAC;AAEtF,KAAK,iCAAiC,GAAG;IACvC,yBAAyB,EAAE,MAAM,CAAC;IAClC,cAAc,EAAE,4BAA4B,CAAC;IAC7C,kBAAkB,EAAE,OAAO,CAAC;IAC5B,eAAe,EAAE,OAAO,CAAC;IACzB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,6BAA6B,EAAE,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;IACvE,kBAAkB,CAAC,EAAE,KAAK,GAAG,aAAa,CAAC;CAC5C,CAAC;AAqEF;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,iCAAiC,GACvC,iCAAiC,CAqBnC"}
|
package/dist/status.js
CHANGED
|
@@ -27,6 +27,13 @@ function createReadiness(input) {
|
|
|
27
27
|
status: 'not-ready'
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
+
if (input.supportsTransaction && input.transactionContext === 'unavailable') {
|
|
31
|
+
return {
|
|
32
|
+
critical: true,
|
|
33
|
+
reason: 'Prisma transaction context requires AsyncLocalStorage support from the host runtime.',
|
|
34
|
+
status: 'not-ready'
|
|
35
|
+
};
|
|
36
|
+
}
|
|
30
37
|
return {
|
|
31
38
|
critical: true,
|
|
32
39
|
status: 'ready'
|
|
@@ -57,6 +64,7 @@ function createHealth(input) {
|
|
|
57
64
|
* @returns The create prisma platform status snapshot result.
|
|
58
65
|
*/
|
|
59
66
|
export function createPrismaPlatformStatusSnapshot(input) {
|
|
67
|
+
const transactionContext = input.transactionContext ?? 'als';
|
|
60
68
|
return {
|
|
61
69
|
details: {
|
|
62
70
|
activeRequestTransactions: input.activeRequestTransactions,
|
|
@@ -66,7 +74,7 @@ export function createPrismaPlatformStatusSnapshot(input) {
|
|
|
66
74
|
supportsDisconnect: input.supportsDisconnect,
|
|
67
75
|
supportsTransaction: input.supportsTransaction,
|
|
68
76
|
transactionAbortSignalSupport: input.transactionAbortSignalSupport,
|
|
69
|
-
transactionContext
|
|
77
|
+
transactionContext
|
|
70
78
|
},
|
|
71
79
|
health: createHealth(input),
|
|
72
80
|
ownership: {
|
|
@@ -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"
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"vitest": "^3.2.4",
|
|
54
|
-
"@fluojs/validation": "^1.0.
|
|
54
|
+
"@fluojs/validation": "^1.0.5"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
57
|
"prebuild": "node ../../tooling/scripts/clean-dist.mjs",
|