@fluojs/prisma 1.1.0 → 1.1.1

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 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 lifecycle 및 ALS 기반 transaction context입니다. `PrismaClient`를 모듈 시스템에 연결하고 자동 연결 관리와 요청 범위 트랜잭션을 제공합니다.
5
+ fluo 애플리케이션을 위한 Node.js 20+ Prisma lifecycle 및 ALS 기반 transaction context입니다. `PrismaClient`를 모듈 시스템에 연결하고 자동 연결 관리와 요청 범위 트랜잭션을 제공합니다.
6
6
 
7
7
  ## 목차
8
8
 
@@ -11,6 +11,7 @@ fluo 애플리케이션을 위한 Prisma lifecycle 및 ALS 기반 transaction co
11
11
  - [빠른 시작](#빠른-시작)
12
12
  - [공통 패턴](#공통-패턴)
13
13
  - [서비스 트랜잭션 경계 (@Transaction)](#서비스-트랜잭션-경계-transaction)
14
+ - [요청 트랜잭션 인터셉터 호환성](#요청-트랜잭션-인터셉터-호환성)
14
15
  - [여러 클라이언트를 위한 이름 있는 등록](#여러-클라이언트를-위한-이름-있는-등록)
15
16
  - [수동 트랜잭션과 current()](#수동-트랜잭션과-current)
16
17
  - [종료와 status 계약](#종료와-status-계약)
@@ -30,7 +31,7 @@ pnpm add @prisma/client
30
31
 
31
32
  ## 사용 시점
32
33
 
33
- - Prisma를 ORM으로 사용하면서 fluo의 의존성 주입 및 라이프사이클 훅과 통합하고 싶을 때.
34
+ - Node.js 20+에서 Prisma를 ORM으로 사용하면서 fluo의 의존성 주입 및 라이프사이클 훅과 통합하고 싶을 때.
34
35
  - 여러 서비스와 리포지토리 사이에서 `tx` 객체를 일일이 전달하지 않고도 트랜잭션 컨텍스트를 안정적으로 공유하고 싶을 때.
35
36
  - 애플리케이션 시작 시 자동 `$connect`, 종료 시 자동 `$disconnect`가 필요할 때.
36
37
 
@@ -94,9 +95,58 @@ export class UserRepository {
94
95
 
95
96
  `@Transaction()` 메서드 호출은 재진입(reentrant)이 가능합니다. 데코레이터가 적용된 메서드가 다른 데코레이터 적용 메서드를 호출하더라도 하나의 동일한 Prisma 트랜잭션 안에서 실행됩니다.
96
97
 
98
+ ### 요청 트랜잭션 인터셉터 호환성
99
+
100
+ `PrismaTransactionInterceptor`는 기존 `@UseInterceptors(...)` request-wide boundary를 위한 deprecated 1.x 호환성 export로 복원되었습니다. 이름 없는 `PrismaModule.forRoot(...)`와 `forRootAsync(...)` 등록이 이 interceptor를 provider 및 export로 제공하며, `PrismaService.requestTransaction(...)`에 위임하고 request `AbortSignal`을 전달합니다.
101
+
102
+ ```typescript
103
+ import { Controller, Post, UseInterceptors } from '@fluojs/http';
104
+ import { PrismaTransactionInterceptor } from '@fluojs/prisma';
105
+
106
+ @Controller('/orders')
107
+ export class OrdersController {
108
+ @Post('/')
109
+ @UseInterceptors(PrismaTransactionInterceptor)
110
+ createOrder() {
111
+ return this.orders.create();
112
+ }
113
+ }
114
+ ```
115
+
116
+ 새 비즈니스 작업에는 서비스 계층 `@Transaction()`을 우선 사용하세요. 전체 요청에 하나의 트랜잭션이 정말 필요하거나 이름 있는/여러 Prisma 등록에서 특정 서비스를 선택해야 한다면 명시적 `requestTransaction(...)`을 사용하세요. 호환성 interceptor는 이름 없는 기본 등록만 대상으로 합니다.
117
+
118
+ 요청 전체 원자성이 정말 필요한 경우에는 application code에서 boundary와 cancellation input을 명시적으로 드러내세요.
119
+
120
+ ```typescript
121
+ import { Inject } from '@fluojs/core';
122
+ import { Controller, Post, type RequestContext } from '@fluojs/http';
123
+ import { PrismaService } from '@fluojs/prisma';
124
+ import { PrismaClient } from '@prisma/client';
125
+
126
+ @Inject(PrismaService, OrdersService)
127
+ @Controller('/orders')
128
+ export class OrdersController {
129
+ constructor(
130
+ private readonly prisma: PrismaService<PrismaClient>,
131
+ private readonly orders: OrdersService,
132
+ ) {}
133
+
134
+ @Post('/checkout')
135
+ checkout(input: CheckoutInput, context: RequestContext) {
136
+ const { request } = context;
137
+ return this.prisma.requestTransaction(
138
+ () => this.orders.checkout(input),
139
+ request.signal,
140
+ );
141
+ }
142
+ }
143
+ ```
144
+
145
+ 이는 서비스 `@Transaction()`을 대체하는 방식이 아니라 좁은 호환성 패턴입니다. 요청 전체 트랜잭션은 HTTP 작업 전체에서 데이터베이스 lock을 유지할 수 있으므로 boundary를 짧고 명시적으로 유지하세요.
146
+
97
147
  ### 여러 클라이언트를 위한 이름 있는 등록
98
148
 
99
- 하나의 애플리케이션 컨테이너 안에서 여러 Prisma Client가 필요하다면 각 등록에 명시적인 `name`을 부여하고 `getPrismaServiceToken(name)`으로 대응되는 토큰을 주입하세요. 이름 있는 클라이언트를 사용할 때는 `@Transaction()`에 해당 서비스로 접근할 수 있는 accessor를 전달하세요.
149
+ 하나의 애플리케이션 컨테이너 안에서 여러 Prisma Client가 필요하다면 각 등록에 명시적인 `name`을 부여하고 `getPrismaServiceToken(name)`으로 대응되는 토큰을 주입하세요. 이름 있는 클라이언트를 사용할 때는 `@Transaction()`에 해당 서비스로 접근할 수 있는 accessor를 전달하세요. 기본 `@Transaction()` 해석은 Prisma service/facade 형태의 속성만 선택합니다. 다른 persistence 통합의 transaction-like 객체는 무시되므로 모호한 host에서는 명시적 accessor를 사용해야 합니다.
100
150
 
101
151
  ```typescript
102
152
  import { Inject } from '@fluojs/core';
@@ -158,16 +208,18 @@ await this.prisma.transaction(async () => {
158
208
 
159
209
  ### 종료와 status 계약
160
210
 
161
- `PrismaService.requestTransaction(...)`은 정상 serving 전과 중에는 사용할 수 있지만, 애플리케이션 shutdown이 시작된 뒤에는 새 요청 범위 트랜잭션을 거부합니다. 종료 중에는 열린 요청 트랜잭션을 abort하고, 가장 바깥 transaction boundary가 settle될 때까지 추적한 다음 `$disconnect()` 실행 전에 drain합니다. 기존 수동 `transaction(...)` boundary 안에서 열린 중첩 `requestTransaction(...)` 호출도 동일합니다. 해당 호출은 ambient Prisma transaction client를 재사용하고, 바깥 boundary가 끝날 때까지 `details.activeRequestTransactions`에 표시되며, 두 번째 Prisma transaction을 열지 않습니다.
211
+ `PrismaService.requestTransaction(...)`은 정상 serving 전과 중에는 사용할 수 있지만, 애플리케이션 shutdown이 시작된 뒤에는 새 요청 범위 트랜잭션을 거부합니다. 새 outer 수동 `transaction(...)` 및 서비스 `@Transaction()` boundary도 shutdown 시작 후에는 거부됩니다. 이미 열린 boundary는 `$disconnect()` 전에 drain되므로 shutdown이 활성 Prisma transaction과 경합하지 않습니다. 종료 중에는 열린 요청 트랜잭션을 abort하고, 가장 바깥 transaction boundary가 settle될 때까지 추적한 다음 `$disconnect()` 실행 전에 drain합니다. 기존 수동 `transaction(...)` boundary 안에서 열린 중첩 `requestTransaction(...)` 호출도 동일합니다. 해당 호출은 ambient Prisma transaction client를 재사용하고, 바깥 boundary가 끝날 때까지 `details.activeRequestTransactions`에 표시되며, 두 번째 Prisma transaction을 열지 않습니다.
162
212
 
163
213
  `createPrismaPlatformStatusSnapshot(...)`와 `PrismaService.createPlatformStatusSnapshot()`은 같은 라이프사이클 계약을 진단 surface에 노출합니다.
164
214
 
165
- - `readiness.status`는 `onModuleInit()`이 클라이언트를 연결하기 전, Prisma가 종료 중이거나 stopped 상태일 때, 그리고 `strictTransactions`가 켜져 있는데 `$transaction(...)`을 지원하지 않을 때 `not-ready`입니다.
215
+ - `readiness.status`는 `onModuleInit()`이 클라이언트를 연결하기 전, Prisma가 종료 중이거나 stopped 상태일 때, `strictTransactions`가 켜져 있는데 `$transaction(...)`을 지원하지 않을 때, 그리고 클라이언트가 interactive transaction을 지원하지만 호스트 런타임이 `AsyncLocalStorage`를 제공하지 않을 때 `not-ready`입니다. ALS 미지원 상태의 readiness reason은 `Prisma transaction context requires AsyncLocalStorage support from the host runtime.`이며 `details.transactionContext`가 `unavailable`로 보고됩니다. 이 상태는 Prisma 클라이언트 자체는 연결되어 있고 기능적으로 정상일 수 있으므로 일반 database readiness 실패와 구분됩니다.
166
216
  - `health.status`는 종료 중 요청 트랜잭션을 drain하는 동안 `degraded`, disconnect 이후 `unhealthy`입니다.
167
217
  - `details.activeRequestTransactions`, `details.lifecycleState`, `details.strictTransactions`, `details.supportsTransaction`, `details.transactionAbortSignalSupport`는 현재 요청 트랜잭션과 트랜잭션 capability 상태를 설명합니다.
168
- - `details.transactionContext: 'als'`는 요청 및 서비스 트랜잭션 경계가 사용하는 async-local transaction context를 식별합니다.
218
+ - `details.transactionContext: 'als'`는 요청 및 서비스 트랜잭션 경계가 사용하는 async-local transaction context를 식별합니다. `details.transactionContext: 'unavailable'`은 호스트 런타임이 사용 가능한 `AsyncLocalStorage`를 노출하지 않았음을 나타내며, 이 경우 `transaction()`과 `requestTransaction()`은 Prisma 트랜잭션을 열기 전에 예외를 던집니다.
169
219
  - `ownership.externallyManaged: false`와 `ownership.ownsResources: true`는 패키지가 fluo 애플리케이션 라이프사이클 안에서 등록된 클라이언트의 `$connect()` / `$disconnect()` lifecycle hook을 소유한다는 의미입니다.
170
220
 
221
+ `details.transactionContext`가 `unavailable`이면 패키지는 동기 stack 기반 컨텍스트로 fallback하지 않습니다. async boundary 사이에서 `current()`를 잃기 때문입니다. fallback boundary는 애플리케이션이 소유합니다. 트랜잭션 컨텍스트 없이도 데이터베이스 접근이 필요한 호출자는 (예: `PRISMA_CLIENT` 토큰을 통해) 원시 `PrismaClient`를 직접 호출하고 자체 일관성 semantics를 관리하거나, `AsyncLocalStorage`를 제공하는 호스트 런타임(Node.js 20+가 문서화된 경로)에서 실행해야 합니다. `unavailable` readiness 상태는 운영적으로 실행 가능한 신호로 취급하세요. health check에 노출하고, 호스트가 ALS를 제공하거나 애플리케이션이 비트랜잭션 접근 경로로 전환할 때까지 트랜잭션 의존 handler로 트래픽을 라우팅하지 마세요.
222
+
171
223
  ### 비동기 설정과 격리
172
224
 
173
225
  주입된 설정이나 다른 비동기 소스에서 Prisma 클라이언트를 만들어야 할 때는 `PrismaModule.forRootAsync(...)`를 사용하세요. 비동기 factory는 애플리케이션 컨테이너마다 한 번 resolve되며, 테스트나 여러 앱을 띄우는 프로세스에서 같은 모듈 정의를 재사용하더라도 별도 bootstrap 사이에서 공유되지 않습니다.
@@ -187,7 +239,7 @@ PrismaModule.forRootAsync({
187
239
 
188
240
  하나의 컴파일된 애플리케이션 안에서는 하위 provider가 동일하게 resolve된 `PrismaService`, ALS 트랜잭션 컨텍스트, 라이프사이클 관리 대상 클라이언트를 공유합니다. 서로 다른 애플리케이션 컨테이너는 독립된 factory 결과를 받으므로 `$connect` / `$disconnect` 소유권과 요청 트랜잭션 상태가 격리됩니다.
189
241
 
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`로 보고됩니다.
242
+ 트랜잭션 경계에는 호스트가 제공하는 `AsyncLocalStorage` 지원이 필요합니다. 패키지 manifest는 `engines.node >=20.0.0`을 선언하며, root wrapper는 문서화된 Node.js 20+ Prisma 통합 경로입니다. `@fluojs/prisma`는 런타임이 노출하는 `globalThis.AsyncLocalStorage` 또는 Node.js의 `process.getBuiltinModule('node:async_hooks')` 호스트 경계를 통해 ALS를 resolve합니다. 두 경로 모두 사용할 수 없거나 host builtin lookup이 실패하면 동기 stack fallback으로 async boundary 사이의 `current()`를 잃는 대신, Prisma 트랜잭션을 열기 전에 `transaction()`과 `requestTransaction()`이 예외를 던집니다. 이 상태는 `createPlatformStatusSnapshot().details.transactionContext`에 `unavailable`로 보고됩니다.
191
243
 
192
244
  ### 수동 모듈 조합
193
245
 
@@ -212,7 +264,7 @@ defineModule(ManualPrismaModule, {
212
264
  ### `PrismaModule`
213
265
 
214
266
  - `PrismaModule.forRoot(options)` / `PrismaModule.forRootAsync(options)`
215
- - `forRoot(...)`와 `forRootAsync(...)`도 이름 있는/scoped 등록을 위해 `name`을 받을 수 있습니다.
267
+ - `forRoot(...)`와 `forRootAsync(...)`도 이름 있는/scoped 등록을 위해 `name`을 받을 수 있으며, 이름 없는 등록을 전역 provider로 export해야 할 때 `global?: boolean`을 받을 수 있습니다.
216
268
  - `forRootAsync(...)`는 client와 transaction 설정을 factory에서 반환하는 DI-aware Prisma 옵션을 받습니다. 모듈 identity와 visibility가 factory 실행 전에 결정되도록 `name` 또는 `global`은 최상위 async 등록 옵션에 전달하세요.
217
269
  - `forRootAsync(...)`는 애플리케이션 컨테이너마다 옵션을 한 번 resolve하여, 별도 bootstrap 사이에서 클라이언트 라이프사이클과 요청 트랜잭션 격리를 보존합니다.
218
270
  - `strictTransactions: true` 설정 시 트랜잭션 미지원 환경에서 즉시 예외를 발생시킵니다.
@@ -225,7 +277,7 @@ defineModule(ManualPrismaModule, {
225
277
  - `current(): TClient | PrismaTransactionClient<TClient>`
226
278
  - 현재 컨텍스트에 맞는 트랜잭션 클라이언트 또는 루트 클라이언트를 반환합니다.
227
279
  - `transaction(fn, options?): Promise<T>`
228
- - 대화형 트랜잭션 내에서 함수를 실행합니다. 이미 트랜잭션 컨텍스트가 활성화되어 있으면 callback은 그 컨텍스트를 재사용하며, 새 Prisma 트랜잭션 경계가 열리지 않기 때문에 중첩 트랜잭션 옵션은 거부됩니다.
280
+ - 대화형 트랜잭션 내에서 함수를 실행합니다. 이미 트랜잭션 컨텍스트가 활성화되어 있으면 callback은 그 컨텍스트를 재사용하며, 새 Prisma 트랜잭션 경계가 열리지 않기 때문에 중첩 트랜잭션 옵션은 거부됩니다. shutdown이 시작된 뒤에는 새 outer transaction boundary를 거부합니다.
229
281
  - `requestTransaction(fn, signal?, options?): Promise<T>`
230
282
  - HTTP 요청 라이프사이클에 특화된 트랜잭션 경계를 실행합니다. Abort를 인식하고, shutdown 중에는 disconnect 전에 열린 요청 트랜잭션을 drain하며, Prisma client가 `signal` 옵션을 거부하면 해당 옵션 없이 재시도합니다. `transaction()`과 마찬가지로 중첩 호출은 활성 트랜잭션 컨텍스트를 재사용하고, 트랜잭션 설정을 조용히 무시하지 않도록 중첩 옵션을 거부합니다.
231
283
 
@@ -233,7 +285,13 @@ Provider가 `current()`, `transaction(...)`, `requestTransaction(...)`, `createP
233
285
 
234
286
  ### `Transaction`
235
287
 
236
- - 서비스 계층 트랜잭션 경계를 위한 표준 TC39 method decorator입니다. 기본적으로 ambient `PrismaService`를 resolve하고, 이름 있는 client에는 accessor를 받을 수 있으며, 외부 경계에는 Prisma transaction option을 전달할 수 있습니다.
288
+ - 서비스 계층 트랜잭션 경계를 위한 표준 TC39 method decorator입니다. 기본적으로 Prisma service/facade 형태의 속성을 resolve하고, 이름 있는 client나 모호한 host에는 accessor를 받을 수 있으며, 외부 경계에는 Prisma transaction option을 전달할 수 있습니다.
289
+
290
+ ### `PrismaTransactionInterceptor` (deprecated 호환성)
291
+
292
+ - 기존 1.x import를 위한 request-wide HTTP 호환성 interceptor입니다.
293
+ - `PrismaService.requestTransaction(...)`에 위임하고 request cancellation을 전달합니다.
294
+ - 새 코드에서는 서비스 `@Transaction()` 또는 명시적 request boundary를 우선 사용하세요.
237
295
 
238
296
  ### `PRISMA_CLIENT` (Token)
239
297
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
4
4
 
5
- Prisma lifecycle and ALS-backed transaction context for fluo applications. Connects a `PrismaClient` to the module system with automatic connection management and request-scoped transactions.
5
+ Node.js 20+ Prisma lifecycle and ALS-backed transaction context for fluo applications. Connects a `PrismaClient` to the module system with automatic connection management and request-scoped transactions.
6
6
 
7
7
  ## Table of Contents
8
8
 
@@ -11,6 +11,7 @@ Prisma lifecycle and ALS-backed transaction context for fluo applications. Conne
11
11
  - [Quick Start](#quick-start)
12
12
  - [Common Patterns](#common-patterns)
13
13
  - [Service Transaction Boundary (@Transaction)](#service-transaction-boundary-transaction)
14
+ - [Request Transaction Interceptor Compatibility](#request-transaction-interceptor-compatibility)
14
15
  - [Named Registrations for Multiple Clients](#named-registrations-for-multiple-clients)
15
16
  - [Manual Transactions and current()](#manual-transactions-and-current)
16
17
  - [Shutdown and Status Contracts](#shutdown-and-status-contracts)
@@ -30,7 +31,7 @@ pnpm add @prisma/client
30
31
 
31
32
  ## When to Use
32
33
 
33
- - When using Prisma as your ORM and you want it integrated with fluo's dependency injection and lifecycle hooks.
34
+ - When using Prisma as your ORM on Node.js 20+ and you want it integrated with fluo's dependency injection and lifecycle hooks.
34
35
  - When you need a reliable way to share a transaction context across multiple services and repositories without passing a `tx` object everywhere.
35
36
  - When you want automatic `$connect` on startup and `$disconnect` on shutdown.
36
37
 
@@ -94,9 +95,58 @@ export class UserRepository {
94
95
 
95
96
  Calls to `@Transaction()` methods are reentrant. If a decorated method calls another decorated method, they share the same underlying Prisma transaction.
96
97
 
98
+ ### Request Transaction Interceptor Compatibility
99
+
100
+ `PrismaTransactionInterceptor` is restored as a deprecated 1.x compatibility export for existing `@UseInterceptors(...)` request-wide boundaries. The unnamed `PrismaModule.forRoot(...)` and `forRootAsync(...)` registrations provide and export it. It delegates to `PrismaService.requestTransaction(...)` and forwards the request `AbortSignal`.
101
+
102
+ ```typescript
103
+ import { Controller, Post, UseInterceptors } from '@fluojs/http';
104
+ import { PrismaTransactionInterceptor } from '@fluojs/prisma';
105
+
106
+ @Controller('/orders')
107
+ export class OrdersController {
108
+ @Post('/')
109
+ @UseInterceptors(PrismaTransactionInterceptor)
110
+ createOrder() {
111
+ return this.orders.create();
112
+ }
113
+ }
114
+ ```
115
+
116
+ Prefer service-layer `@Transaction()` for new business operations. Use explicit `requestTransaction(...)` when a complete request truly needs one transaction or named/multiple Prisma registrations must select a specific service; the compatibility interceptor targets only the unnamed default registration.
117
+
118
+ When request-wide atomicity is genuinely required, make the boundary and cancellation input visible in application code:
119
+
120
+ ```typescript
121
+ import { Inject } from '@fluojs/core';
122
+ import { Controller, Post, type RequestContext } from '@fluojs/http';
123
+ import { PrismaService } from '@fluojs/prisma';
124
+ import { PrismaClient } from '@prisma/client';
125
+
126
+ @Inject(PrismaService, OrdersService)
127
+ @Controller('/orders')
128
+ export class OrdersController {
129
+ constructor(
130
+ private readonly prisma: PrismaService<PrismaClient>,
131
+ private readonly orders: OrdersService,
132
+ ) {}
133
+
134
+ @Post('/checkout')
135
+ checkout(input: CheckoutInput, context: RequestContext) {
136
+ const { request } = context;
137
+ return this.prisma.requestTransaction(
138
+ () => this.orders.checkout(input),
139
+ request.signal,
140
+ );
141
+ }
142
+ }
143
+ ```
144
+
145
+ This is a narrow compatibility pattern, not a replacement for service `@Transaction()`. A request-wide transaction can hold database locks for the entire HTTP operation, so keep the boundary short and explicit.
146
+
97
147
  ### Named Registrations for Multiple Clients
98
148
 
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.
149
+ 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. Default `@Transaction()` resolution only selects Prisma service/facade-shaped properties; transaction-like objects from other persistence integrations are ignored so ambiguous hosts must use an explicit accessor.
100
150
 
101
151
  ```typescript
102
152
  import { Inject } from '@fluojs/core';
@@ -159,16 +209,18 @@ When `transaction()` is called while a transaction context is already active, `P
159
209
 
160
210
  ### Shutdown and Status Contracts
161
211
 
162
- `PrismaService.requestTransaction(...)` is available before and during normal serving, but new request-scoped transactions are rejected once application shutdown has started. During shutdown, open request transactions are aborted, tracked until their outer transaction boundary has settled, and drained before `$disconnect()` runs. This includes nested `requestTransaction(...)` calls opened inside an existing manual `transaction(...)` boundary: they reuse the ambient Prisma transaction client, stay visible in `details.activeRequestTransactions` until the outer boundary finishes, and do not open a second Prisma transaction.
212
+ `PrismaService.requestTransaction(...)` is available before and during normal serving, but new request-scoped transactions are rejected once application shutdown has started. New outer manual `transaction(...)` and service `@Transaction()` boundaries are also rejected after shutdown begins; boundaries that were already open are drained before `$disconnect()` so shutdown does not race an active Prisma transaction. During shutdown, open request transactions are aborted, tracked until their outer transaction boundary has settled, and drained before `$disconnect()` runs. This includes nested `requestTransaction(...)` calls opened inside an existing manual `transaction(...)` boundary: they reuse the ambient Prisma transaction client, stay visible in `details.activeRequestTransactions` until the outer boundary finishes, and do not open a second Prisma transaction.
163
213
 
164
214
  `createPrismaPlatformStatusSnapshot(...)` and `PrismaService.createPlatformStatusSnapshot()` expose the same lifecycle contract to diagnostics surfaces:
165
215
 
166
- - `readiness.status` is `not-ready` before `onModuleInit()` connects the client, while Prisma is shutting down or stopped, and when `strictTransactions` is enabled without `$transaction(...)` support.
216
+ - `readiness.status` is `not-ready` before `onModuleInit()` connects the client, while Prisma is shutting down or stopped, when `strictTransactions` is enabled without `$transaction(...)` support, and when the host runtime does not provide `AsyncLocalStorage` while the client supports interactive transactions. In the ALS-unavailable case the readiness reason is `Prisma transaction context requires AsyncLocalStorage support from the host runtime.` and `details.transactionContext` reports `unavailable`; this state is distinct from an ordinary database readiness failure because the Prisma client itself may be connected and otherwise functional.
167
217
  - `health.status` is `degraded` while request transactions are draining during shutdown and `unhealthy` after disconnect.
168
218
  - `details.activeRequestTransactions`, `details.lifecycleState`, `details.strictTransactions`, `details.supportsTransaction`, and `details.transactionAbortSignalSupport` describe the current request transaction and transaction-capability state.
169
- - `details.transactionContext: 'als'` identifies the async-local transaction context used by request and service transaction boundaries.
219
+ - `details.transactionContext: 'als'` identifies the async-local transaction context used by request and service transaction boundaries. `details.transactionContext: 'unavailable'` indicates the host runtime did not expose a usable `AsyncLocalStorage`, so `transaction()` and `requestTransaction()` reject before opening a Prisma transaction.
170
220
  - `ownership.externallyManaged: false` and `ownership.ownsResources: true` mean the package owns the registered client's `$connect()` / `$disconnect()` lifecycle hooks inside the fluo application lifecycle.
171
221
 
222
+ When `details.transactionContext` is `unavailable`, the package does not fall back to a synchronous stack-based context because that would lose `current()` across async boundaries. The application owns the fallback boundary: callers that still need database access without a transaction context must invoke the raw `PrismaClient` directly (for example through the `PRISMA_CLIENT` token) and manage their own consistency semantics, or run on a host runtime that provides `AsyncLocalStorage` (Node.js 20+ is the documented path). Treat the `unavailable` readiness state as operationally actionable — surface it in health checks and route traffic away from transaction-dependent handlers until the host provides ALS or the application switches to a non-transactional access path.
223
+
172
224
  ### Async Configuration and Isolation
173
225
 
174
226
  Use `PrismaModule.forRootAsync(...)` when the Prisma client must be created from injected configuration or another async source. The async factory is resolved once per application container and is not shared across separate bootstraps, even when the same module definition is reused in tests or multi-app processes.
@@ -188,7 +240,7 @@ PrismaModule.forRootAsync({
188
240
 
189
241
  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.
190
242
 
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.
243
+ Transaction boundaries require host-provided `AsyncLocalStorage` support. The package manifest declares `engines.node >=20.0.0`, and the root wrapper is the documented Node.js 20+ Prisma integration path. `@fluojs/prisma` resolves ALS 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 or the host builtin lookup fails, `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
244
 
193
245
  ### Manual Module Composition
194
246
 
@@ -213,7 +265,7 @@ defineModule(ManualPrismaModule, {
213
265
  ### `PrismaModule`
214
266
 
215
267
  - `PrismaModule.forRoot(options)` / `PrismaModule.forRootAsync(options)`
216
- - `forRoot(...)` and `forRootAsync(...)` also accept `name` for named/scoped registrations.
268
+ - `forRoot(...)` and `forRootAsync(...)` also accept `name` for named/scoped registrations, and `global?: boolean` for unnamed registrations that should export their providers globally.
217
269
  - `forRootAsync(...)` accepts DI-aware Prisma options whose factory returns the client and transaction settings; pass `name` or `global` on the top-level async registration so module identity and visibility are decided before the factory runs.
218
270
  - `forRootAsync(...)` resolves options once per application container, preserving client lifecycle and request transaction isolation across separate bootstraps.
219
271
  - Supports `strictTransactions: true` to throw if transaction support is missing.
@@ -226,7 +278,7 @@ defineModule(ManualPrismaModule, {
226
278
  - `current(): TClient | PrismaTransactionClient<TClient>`
227
279
  - Returns the ambient transaction client or the root client.
228
280
  - `transaction(fn, options?): Promise<T>`
229
- - Runs a function within an interactive transaction. If a transaction context is already active, the callback reuses that context; nested transaction options are rejected because no new Prisma transaction boundary is opened.
281
+ - Runs a function within an interactive transaction. If a transaction context is already active, the callback reuses that context; nested transaction options are rejected because no new Prisma transaction boundary is opened. New outer transaction boundaries are rejected once shutdown starts.
230
282
  - `requestTransaction(fn, signal?, options?): Promise<T>`
231
283
  - 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.
232
284
 
@@ -234,7 +286,13 @@ Use `PrismaService<TClient>` when a provider only needs wrapper methods such as
234
286
 
235
287
  ### `Transaction`
236
288
 
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.
289
+ - Standard TC39 method decorator for service-layer transaction boundaries. It resolves a Prisma service/facade-shaped property by default, accepts an accessor for named clients or ambiguous hosts, and can forward Prisma transaction options to the outer boundary.
290
+
291
+ ### `PrismaTransactionInterceptor` (deprecated compatibility)
292
+
293
+ - Request-wide HTTP compatibility interceptor for existing 1.x imports.
294
+ - Delegates to `PrismaService.requestTransaction(...)` and forwards request cancellation.
295
+ - Prefer service `@Transaction()` or an explicit request boundary in new code.
238
296
 
239
297
  ### `PRISMA_CLIENT` (Token)
240
298
 
package/dist/module.d.ts CHANGED
@@ -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 related Prisma tokens.
16
+ * @returns A module definition that exports `PrismaService`, compatibility interceptor, 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
  /**
@@ -1 +1 @@
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"}
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;AAShE,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;AA8MF;;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,6 +1,7 @@
1
1
  import { defineModule } from '@fluojs/runtime';
2
2
  import { PrismaService } from './service.js';
3
3
  import { getPrismaClientToken, getPrismaOptionsToken, getPrismaServiceToken } from './tokens.js';
4
+ import { PrismaTransactionInterceptor } from './transaction.js';
4
5
  const PRISMA_NORMALIZED_OPTIONS = Symbol('fluo.prisma.normalized-options');
5
6
  function isObjectLike(value) {
6
7
  return typeof value === 'object' && value !== null || typeof value === 'function';
@@ -57,7 +58,7 @@ function createPrismaRuntimeProviders(normalizedOptionsProvider, name) {
57
58
  }, ...(name === undefined ? [createPrismaServiceProvider(PrismaService, clientToken, optionsToken), {
58
59
  provide: getPrismaServiceToken(),
59
60
  useExisting: PrismaService
60
- }] : [createPrismaServiceProvider(getPrismaServiceToken(name), clientToken, optionsToken)])];
61
+ }, PrismaTransactionInterceptor] : [createPrismaServiceProvider(getPrismaServiceToken(name), clientToken, optionsToken)])];
61
62
  }
62
63
  function buildPrismaModule(options) {
63
64
  class PrismaRootModuleDefinition {}
@@ -66,7 +67,7 @@ function buildPrismaModule(options) {
66
67
  throw new Error('Named Prisma registrations are scoped and cannot be registered globally.');
67
68
  }
68
69
  return defineModule(PrismaRootModuleDefinition, {
69
- exports: normalizedOptions.name === undefined ? [PrismaService, getPrismaServiceToken(), getPrismaClientToken(), getPrismaOptionsToken()] : [getPrismaServiceToken(normalizedOptions.name), getPrismaClientToken(normalizedOptions.name), getPrismaOptionsToken(normalizedOptions.name)],
70
+ exports: normalizedOptions.name === undefined ? [PrismaService, PrismaTransactionInterceptor, getPrismaServiceToken(), getPrismaClientToken(), getPrismaOptionsToken()] : [getPrismaServiceToken(normalizedOptions.name), getPrismaClientToken(normalizedOptions.name), getPrismaOptionsToken(normalizedOptions.name)],
70
71
  global: normalizedOptions.name === undefined ? normalizedOptions.global : false,
71
72
  providers: createPrismaRuntimeProviders({
72
73
  provide: getPrismaNormalizedOptionsToken(normalizedOptions.name),
@@ -95,7 +96,7 @@ function buildPrismaModuleAsync(options) {
95
96
  }
96
97
  };
97
98
  return defineModule(PrismaAsyncModuleDefinition, {
98
- exports: normalizedName === undefined ? [PrismaService, getPrismaServiceToken(), getPrismaClientToken(), getPrismaOptionsToken()] : [getPrismaServiceToken(normalizedName), getPrismaClientToken(normalizedName), getPrismaOptionsToken(normalizedName)],
99
+ exports: normalizedName === undefined ? [PrismaService, PrismaTransactionInterceptor, getPrismaServiceToken(), getPrismaClientToken(), getPrismaOptionsToken()] : [getPrismaServiceToken(normalizedName), getPrismaClientToken(normalizedName), getPrismaOptionsToken(normalizedName)],
99
100
  global: normalizedName === undefined ? options.global ?? false : false,
100
101
  providers: createPrismaRuntimeProviders(normalizedOptionsProvider, normalizedName)
101
102
  });
@@ -109,7 +110,7 @@ export class PrismaModule {
109
110
  * Registers Prisma providers from static options.
110
111
  *
111
112
  * @param options Prisma module options with client handle and strict transaction mode.
112
- * @returns A module definition that exports `PrismaService` and related Prisma tokens.
113
+ * @returns A module definition that exports `PrismaService`, compatibility interceptor, and related Prisma tokens.
113
114
  */
114
115
  static forRoot(options) {
115
116
  return buildPrismaModule(options);
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Marks an internal Prisma service or facade handle for default `@Transaction()` resolution.
3
+ *
4
+ * @internal
5
+ */
6
+ export declare function markPrismaServiceHandle<THandle extends object>(handle: THandle): THandle;
7
+ /**
8
+ * Checks whether a value is an internally marked Prisma service or facade handle.
9
+ *
10
+ * @internal
11
+ */
12
+ export declare function isPrismaServiceHandle(value: unknown): value is object;
13
+ //# sourceMappingURL=prisma-service-brand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-service-brand.d.ts","sourceRoot":"","sources":["../src/prisma-service-brand.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,SAAS,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAIxF;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAMrE"}
@@ -0,0 +1,23 @@
1
+ const brandedPrismaServiceHandles = new WeakSet();
2
+
3
+ /**
4
+ * Marks an internal Prisma service or facade handle for default `@Transaction()` resolution.
5
+ *
6
+ * @internal
7
+ */
8
+ export function markPrismaServiceHandle(handle) {
9
+ brandedPrismaServiceHandles.add(handle);
10
+ return handle;
11
+ }
12
+
13
+ /**
14
+ * Checks whether a value is an internally marked Prisma service or facade handle.
15
+ *
16
+ * @internal
17
+ */
18
+ export function isPrismaServiceHandle(value) {
19
+ if (typeof value !== 'object' && typeof value !== 'function' || value === null) {
20
+ return false;
21
+ }
22
+ return brandedPrismaServiceHandles.has(value);
23
+ }
package/dist/service.d.ts CHANGED
@@ -15,6 +15,7 @@ export declare class PrismaService<TClient extends PrismaClientLike<TTransaction
15
15
  private readonly serviceOptions;
16
16
  private readonly transactions;
17
17
  private readonly activeRequestTransactions;
18
+ private readonly activeTransactionBoundaries;
18
19
  private transactionAbortSignalSupport;
19
20
  private lifecycleState;
20
21
  constructor(client: TClient, serviceOptions?: PrismaServiceOptions);
@@ -94,6 +95,7 @@ export declare class PrismaService<TClient extends PrismaClientLike<TTransaction
94
95
  private runWithRequestTransactionClient;
95
96
  private runNestedRequestTransaction;
96
97
  private assertRequestTransactionsAvailable;
98
+ private assertTransactionBoundariesAvailable;
97
99
  private assertTransactionContextAvailable;
98
100
  private throwIfRequestAborted;
99
101
  private runRequestTransactionWithAbortSignal;
@@ -104,6 +106,8 @@ export declare class PrismaService<TClient extends PrismaClientLike<TTransaction
104
106
  private withTransactionAbortSignal;
105
107
  private trackActiveRequestTransaction;
106
108
  private untrackActiveRequestTransaction;
109
+ private trackActiveTransactionBoundary;
110
+ private untrackActiveTransactionBoundary;
107
111
  }
108
112
  /**
109
113
  * Injection-facing Prisma facade type that combines the Fluo wrapper methods with the registered Prisma client surface.
@@ -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;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"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
package/dist/service.js CHANGED
@@ -4,15 +4,18 @@ 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 { createAbortError, createRequestAbortContext, raceWithAbort, trackActiveRequestTransaction, untrackActiveRequestTransaction } from '@fluojs/runtime';
8
7
  import { Inject } from '@fluojs/core';
8
+ import { createAbortError, createRequestAbortContext, raceWithAbort, trackActiveRequestTransaction, untrackActiveRequestTransaction } from '@fluojs/runtime';
9
+ import { markPrismaServiceHandle } from './prisma-service-brand.js';
9
10
  import { createPrismaPlatformStatusSnapshot } from './status.js';
10
11
  import { PRISMA_CLIENT, PRISMA_OPTIONS } from './tokens.js';
11
12
  const NESTED_TRANSACTION_OPTIONS_NOT_SUPPORTED_ERROR = 'Nested Prisma transaction options are not supported because the active transaction context is reused.';
12
13
  const REQUEST_TRANSACTION_UNAVAILABLE_ERROR = 'Prisma request transactions are not available during shutdown.';
14
+ const TRANSACTION_BOUNDARY_UNAVAILABLE_ERROR = 'Prisma transaction boundaries are not available during shutdown.';
13
15
  const TRANSACTION_CONTEXT_UNAVAILABLE_ERROR = 'Prisma transaction context requires AsyncLocalStorage support from the host runtime.';
14
16
  function createCurrentClientPrismaFacade(target) {
15
- return new Proxy(target, {
17
+ markPrismaServiceHandle(target);
18
+ return markPrismaServiceHandle(new Proxy(target, {
16
19
  get(service, prop, receiver) {
17
20
  if (prop in service) {
18
21
  return Reflect.get(service, prop, receiver);
@@ -21,7 +24,7 @@ function createCurrentClientPrismaFacade(target) {
21
24
  const value = Reflect.get(currentClient, prop, currentClient);
22
25
  return typeof value === 'function' ? value.bind(currentClient) : value;
23
26
  }
24
- });
27
+ }));
25
28
  }
26
29
  class AsyncLocalStorageTransactionContextStore {
27
30
  kind = 'als';
@@ -49,7 +52,11 @@ function resolveAsyncLocalStorageConstructor(host = globalThis) {
49
52
  if (typeof host.AsyncLocalStorage === 'function') {
50
53
  return host.AsyncLocalStorage;
51
54
  }
52
- return host.process?.getBuiltinModule?.('node:async_hooks').AsyncLocalStorage;
55
+ try {
56
+ return host.process?.getBuiltinModule?.('node:async_hooks')?.AsyncLocalStorage;
57
+ } catch {
58
+ return undefined;
59
+ }
53
60
  }
54
61
  function createTransactionContextStore() {
55
62
  const AsyncLocalStorage = resolveAsyncLocalStorageConstructor();
@@ -73,6 +80,7 @@ class PrismaService {
73
80
  }
74
81
  transactions = createTransactionContextStore();
75
82
  activeRequestTransactions = new Set();
83
+ activeTransactionBoundaries = new Set();
76
84
  transactionAbortSignalSupport = 'unknown';
77
85
  lifecycleState = 'created';
78
86
  constructor(client, serviceOptions = {
@@ -80,6 +88,7 @@ class PrismaService {
80
88
  }) {
81
89
  this.client = client;
82
90
  this.serviceOptions = serviceOptions;
91
+ markPrismaServiceHandle(this);
83
92
  this.installCurrentClientFacade();
84
93
  }
85
94
 
@@ -136,23 +145,29 @@ class PrismaService {
136
145
  }
137
146
  return fn();
138
147
  }
139
- if (typeof this.client.$transaction !== 'function') {
140
- if (this.serviceOptions.strictTransactions) {
141
- throw new Error('Transaction not supported: Prisma client does not implement $transaction.');
142
- }
143
- return fn();
144
- }
145
- this.assertTransactionContextAvailable();
146
- const deferredRequestTransactionHandles = new Set();
148
+ this.assertTransactionBoundariesAvailable();
149
+ const activeTransaction = this.trackActiveTransactionBoundary();
147
150
  try {
148
- return await run(transactionClient => this.transactions.run({
149
- client: transactionClient,
150
- deferredRequestTransactionHandles
151
- }, fn), options);
152
- } finally {
153
- for (const handle of deferredRequestTransactionHandles) {
154
- this.untrackActiveRequestTransaction(handle);
151
+ if (typeof this.client.$transaction !== 'function') {
152
+ if (this.serviceOptions.strictTransactions) {
153
+ throw new Error('Transaction not supported: Prisma client does not implement $transaction.');
154
+ }
155
+ return await fn();
156
+ }
157
+ this.assertTransactionContextAvailable();
158
+ const deferredRequestTransactionHandles = new Set();
159
+ try {
160
+ return await run(transactionClient => this.transactions.run({
161
+ client: transactionClient,
162
+ deferredRequestTransactionHandles
163
+ }, fn), options);
164
+ } finally {
165
+ for (const handle of deferredRequestTransactionHandles) {
166
+ this.untrackActiveRequestTransaction(handle);
167
+ }
155
168
  }
169
+ } finally {
170
+ this.untrackActiveTransactionBoundary(activeTransaction);
156
171
  }
157
172
  }
158
173
  async onModuleInit() {
@@ -167,6 +182,7 @@ class PrismaService {
167
182
  transaction.abort(new Error('Application shutdown interrupted an open request transaction.'));
168
183
  }
169
184
  await Promise.allSettled(Array.from(this.activeRequestTransactions, transaction => transaction.settled));
185
+ await Promise.allSettled(Array.from(this.activeTransactionBoundaries, transaction => transaction.settled));
170
186
  if (typeof this.client.$disconnect === 'function') {
171
187
  await this.client.$disconnect();
172
188
  }
@@ -295,6 +311,11 @@ class PrismaService {
295
311
  throw new Error(REQUEST_TRANSACTION_UNAVAILABLE_ERROR);
296
312
  }
297
313
  }
314
+ assertTransactionBoundariesAvailable() {
315
+ if (this.lifecycleState === 'shutting-down' || this.lifecycleState === 'stopped') {
316
+ throw new Error(TRANSACTION_BOUNDARY_UNAVAILABLE_ERROR);
317
+ }
318
+ }
298
319
  assertTransactionContextAvailable() {
299
320
  if (this.transactions.kind === 'unavailable') {
300
321
  throw new Error(TRANSACTION_CONTEXT_UNAVAILABLE_ERROR);
@@ -368,6 +389,23 @@ class PrismaService {
368
389
  untrackActiveRequestTransaction(handle) {
369
390
  untrackActiveRequestTransaction(this.activeRequestTransactions, handle);
370
391
  }
392
+ trackActiveTransactionBoundary() {
393
+ let settle;
394
+ const active = {
395
+ settled: new Promise(resolve => {
396
+ settle = resolve;
397
+ })
398
+ };
399
+ this.activeTransactionBoundaries.add(active);
400
+ return {
401
+ active,
402
+ settle
403
+ };
404
+ }
405
+ untrackActiveTransactionBoundary(handle) {
406
+ this.activeTransactionBoundaries.delete(handle.active);
407
+ handle.settle();
408
+ }
371
409
  static {
372
410
  _initClass();
373
411
  }
@@ -1,4 +1,9 @@
1
+ import type { CallHandler, Interceptor, InterceptorContext } from '@fluojs/http';
2
+ import { PrismaService } from './service.js';
3
+ import type { PrismaClientLike } from './types.js';
1
4
  type TransactionalPrismaService<TOptions = unknown> = {
5
+ createPlatformStatusSnapshot(): unknown;
6
+ current(): unknown;
2
7
  transaction<T>(fn: () => Promise<T>, options?: TOptions): Promise<T>;
3
8
  };
4
9
  type TransactionAccessor<THost, TOptions> = (self: THost) => TransactionalPrismaService<TOptions>;
@@ -18,5 +23,27 @@ type TransactionMethod<THost, TArgs extends unknown[], TResult> = (this: THost,
18
23
  * @returns A standard method decorator that runs the original method inside a Prisma transaction boundary.
19
24
  */
20
25
  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>;
26
+ /**
27
+ * Compatibility HTTP interceptor that opens a Prisma request transaction around a routed handler.
28
+ *
29
+ * @remarks
30
+ * This deprecated 1.x bridge forwards the request `AbortSignal` to `PrismaService.requestTransaction(...)` and is
31
+ * registered only by the unnamed `PrismaModule` entrypoint. Prefer service-layer `@Transaction()` or an explicit
32
+ * request boundary for new code.
33
+ *
34
+ * @deprecated Prefer service-layer `@Transaction()` or explicit `PrismaService.requestTransaction(...)`.
35
+ */
36
+ export declare class PrismaTransactionInterceptor implements Interceptor {
37
+ private readonly prisma;
38
+ constructor(prisma: PrismaService<PrismaClientLike>);
39
+ /**
40
+ * Runs the downstream handler inside the compatibility request transaction.
41
+ *
42
+ * @param context Interceptor context containing the request cancellation signal.
43
+ * @param next Downstream handler chain.
44
+ * @returns The downstream result after the request transaction settles.
45
+ */
46
+ intercept(context: InterceptorContext, next: CallHandler): Promise<unknown>;
47
+ }
21
48
  export {};
22
49
  //# sourceMappingURL=transaction.d.ts.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAGjF,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,KAAK,0BAA0B,CAAC,QAAQ,GAAG,OAAO,IAAI;IACpD,4BAA4B,IAAI,OAAO,CAAC;IACxC,OAAO,IAAI,OAAO,CAAC;IACnB,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;AAuEtB;;;;;;;;;;;;;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;AAED;;;;;;;;;GASG;AACH,qBACa,4BAA6B,YAAW,WAAW;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,aAAa,CAAC,gBAAgB,CAAC;IAEpE;;;;;;OAMG;IACG,SAAS,CAAC,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;CAGlF"}
@@ -1,5 +1,14 @@
1
- function hasTransaction(value) {
2
- return typeof value === 'object' && value !== null && 'transaction' in value && typeof value.transaction === 'function';
1
+ let _initClass;
2
+ function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }
3
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
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
+ 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
+ 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 { Inject } from '@fluojs/core';
8
+ import { isPrismaServiceHandle } from './prisma-service-brand.js';
9
+ import { PrismaService } from './service.js';
10
+ function isPrismaServiceLike(value) {
11
+ return typeof value === 'object' && value !== null && isPrismaServiceHandle(value) && 'createPlatformStatusSnapshot' in value && typeof value.createPlatformStatusSnapshot === 'function' && 'current' in value && typeof value.current === 'function' && 'transaction' in value && typeof value.transaction === 'function';
3
12
  }
4
13
  function readProperty(value, property) {
5
14
  if (typeof value !== 'object' && typeof value !== 'function' || value === null) {
@@ -7,22 +16,27 @@ function readProperty(value, property) {
7
16
  }
8
17
  return Reflect.get(value, property);
9
18
  }
19
+ function addPrismaServiceCandidate(candidates, value) {
20
+ if (!isPrismaServiceLike(value) || candidates.includes(value)) {
21
+ return;
22
+ }
23
+ candidates.push(value);
24
+ }
10
25
  function resolveDefaultPrismaService(self) {
26
+ const candidates = [];
11
27
  const directPrisma = readProperty(self, 'prisma');
12
- if (hasTransaction(directPrisma)) {
13
- return directPrisma;
14
- }
15
- if (hasTransaction(self)) {
16
- return self;
17
- }
28
+ addPrismaServiceCandidate(candidates, directPrisma);
29
+ addPrismaServiceCandidate(candidates, self);
18
30
  for (const value of Object.values(Object(self))) {
19
- if (hasTransaction(value)) {
20
- return value;
21
- }
31
+ addPrismaServiceCandidate(candidates, value);
22
32
  const nestedPrisma = readProperty(value, 'prisma');
23
- if (hasTransaction(nestedPrisma)) {
24
- return nestedPrisma;
25
- }
33
+ addPrismaServiceCandidate(candidates, nestedPrisma);
34
+ }
35
+ if (candidates.length === 1) {
36
+ return candidates[0];
37
+ }
38
+ if (candidates.length > 1) {
39
+ throw new Error('Ambiguous PrismaService resolution for @Transaction(). Provide an explicit accessor function.');
26
40
  }
27
41
  throw new Error('Unable to resolve PrismaService for @Transaction(). Provide an accessor function.');
28
42
  }
@@ -65,4 +79,39 @@ export function Transaction(input) {
65
79
  return prisma.transaction(() => value.apply(this, args), options);
66
80
  };
67
81
  };
68
- }
82
+ }
83
+
84
+ /**
85
+ * Compatibility HTTP interceptor that opens a Prisma request transaction around a routed handler.
86
+ *
87
+ * @remarks
88
+ * This deprecated 1.x bridge forwards the request `AbortSignal` to `PrismaService.requestTransaction(...)` and is
89
+ * registered only by the unnamed `PrismaModule` entrypoint. Prefer service-layer `@Transaction()` or an explicit
90
+ * request boundary for new code.
91
+ *
92
+ * @deprecated Prefer service-layer `@Transaction()` or explicit `PrismaService.requestTransaction(...)`.
93
+ */
94
+ let _PrismaTransactionInt;
95
+ class PrismaTransactionInterceptor {
96
+ static {
97
+ [_PrismaTransactionInt, _initClass] = _applyDecs(this, [Inject(PrismaService)], []).c;
98
+ }
99
+ constructor(prisma) {
100
+ this.prisma = prisma;
101
+ }
102
+
103
+ /**
104
+ * Runs the downstream handler inside the compatibility request transaction.
105
+ *
106
+ * @param context Interceptor context containing the request cancellation signal.
107
+ * @param next Downstream handler chain.
108
+ * @returns The downstream result after the request transaction settles.
109
+ */
110
+ async intercept(context, next) {
111
+ return this.prisma.requestTransaction(() => next.handle(), context.requestContext.request.signal);
112
+ }
113
+ static {
114
+ _initClass();
115
+ }
116
+ }
117
+ export { _PrismaTransactionInt as PrismaTransactionInterceptor };
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "transaction",
10
10
  "als"
11
11
  ],
12
- "version": "1.1.0",
12
+ "version": "1.1.1",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -36,10 +36,10 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@fluojs/core": "^1.0.3",
40
- "@fluojs/http": "^1.1.2",
41
- "@fluojs/di": "^1.1.0",
42
- "@fluojs/runtime": "^1.1.8"
39
+ "@fluojs/core": "^1.1.0",
40
+ "@fluojs/http": "^2.0.1",
41
+ "@fluojs/di": "^2.0.0",
42
+ "@fluojs/runtime": "^2.0.1"
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.5"
54
+ "@fluojs/validation": "^1.0.6"
55
55
  },
56
56
  "scripts": {
57
57
  "prebuild": "node ../../tooling/scripts/clean-dist.mjs",