@fluojs/drizzle 1.1.1 → 2.0.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 CHANGED
@@ -14,6 +14,7 @@ Node.js 전용 트랜잭션 인지형 데이터베이스 래퍼와 선택적 dis
14
14
  - [서비스 트랜잭션 경계 (@Transaction)](#서비스-트랜잭션-경계-transaction)
15
15
  - [수동 트랜잭션과 current()](#수동-트랜잭션과-current)
16
16
  - [요청 전체 컨트롤러 경계](#요청-전체-컨트롤러-경계)
17
+ - [이름 있는 클라이언트](#이름-있는-클라이언트)
17
18
  - [종료와 상태 계약](#종료와-상태-계약)
18
19
  - [수동 모듈 구성](#수동-모듈-구성)
19
20
  - [공개 API 개요](#공개-api-개요)
@@ -23,14 +24,16 @@ Node.js 전용 트랜잭션 인지형 데이터베이스 래퍼와 선택적 dis
23
24
  ## 설치
24
25
 
25
26
  ```bash
26
- npm install @fluojs/drizzle drizzle-orm
27
+ npm install @fluojs/drizzle drizzle-orm@^0.45.2
27
28
  # 사용하는 Drizzle adapter에 맞는 driver도 함께 설치하세요. 예:
28
29
  npm install pg
29
30
  ```
30
31
 
32
+ `@fluojs/drizzle`은 Drizzle ORM `>=0.45.2`를 요구합니다. 더 오래된 Drizzle ORM release를 사용하던 consumer는 이 major `@fluojs/drizzle` release를 적용하기 전에 peer를 업그레이드하고 lockfile을 갱신해야 합니다. fluo integration API는 그대로지만, 애플리케이션은 업그레이드된 ORM을 대상으로 driver별 query 및 migration test를 실행해야 합니다.
33
+
31
34
  ## 런타임 지원
32
35
 
33
- 루트 `@fluojs/drizzle` 패키지는 현재 Node.js 20+ 통합입니다. ambient transaction context를 유지하기 위해 Node의 `node:async_hooks` 모듈을 import하고, package manifest `engines.node >=20.0.0`을 선언합니다.
36
+ 루트 `@fluojs/drizzle` 패키지는 Node.js `>=24.0.0 <27`을 요구합니다. ambient transaction context를 유지하기 위해 Node의 `node:async_hooks` 모듈을 import하며 패키지 자체의 지원 범위를 package manifest 선언합니다. Node 20 및 Node 22 host를 Node.js `>=24.0.0 <27`로 올리세요. Node 24 미만과 Node 27 이상은 지원하지 않습니다.
34
37
 
35
38
  Drizzle ORM 자체는 Bun SQL이나 Cloudflare D1 같은 driver도 대상으로 할 수 있지만, 비 Node transaction-context adapter가 문서화되기 전까지 해당 driver runtime은 이 fluo wrapper 범위 밖입니다.
36
39
 
@@ -38,14 +41,14 @@ Drizzle ORM 자체는 Bun SQL이나 Cloudflare D1 같은 driver도 대상으로
38
41
 
39
42
  ## 사용 시점
40
43
 
41
- - Node.js 20+ 애플리케이션에서 Drizzle을 다른 fluo 모듈과 같은 DI·모듈·라이프사이클 모델 안에 넣고 싶을 때
44
+ - Node.js `>=24.0.0 <27` 애플리케이션에서 Drizzle을 다른 fluo 모듈과 같은 DI·모듈·라이프사이클 모델 안에 넣고 싶을 때
42
45
  - repository 코드가 root handle과 현재 트랜잭션 handle 사이를 `current()` 하나로 다루고 싶을 때
43
46
  - 애플리케이션 종료 시 underlying driver 정리 로직도 함께 실행해야 할 때
44
47
 
45
48
  ## 빠른 시작
46
49
 
47
50
  ```ts
48
- import { ConfigService } from '@fluojs/config';
51
+ import { ConfigModule, ConfigService } from '@fluojs/config';
49
52
  import { Module } from '@fluojs/core';
50
53
  import { DrizzleModule } from '@fluojs/drizzle';
51
54
  import { drizzle } from 'drizzle-orm/node-postgres';
@@ -53,6 +56,12 @@ import { Pool } from 'pg';
53
56
 
54
57
  @Module({
55
58
  imports: [
59
+ ConfigModule.forRoot({
60
+ global: true,
61
+ processEnv: {
62
+ DATABASE_URL: process.env.DATABASE_URL,
63
+ },
64
+ }),
56
65
  DrizzleModule.forRootAsync({
57
66
  inject: [ConfigService],
58
67
  useFactory: async (config: ConfigService) => {
@@ -73,6 +82,8 @@ import { Pool } from 'pg';
73
82
  export class AppModule {}
74
83
  ```
75
84
 
85
+ `forRootAsync(...)`는 factory 의존성으로 `inject`와 `useFactory`만 받으며 NestJS `imports`, `useClass`, `useExisting`, decorator metadata를 탐색하지 않습니다. 생성되는 async module에는 `imports`가 없으므로 sibling module이 export하거나 parent module이 import한 token은 option provider에 보이지 않습니다. 대신 factory 의존성을 global module로 등록하세요. 위의 `ConfigModule.forRoot(...)` 등록은 기본적으로 `ConfigService`를 전역 export하며, 생성된 async Drizzle module이 `ConfigService`를 볼 수 있는 전역 export임을 명확히 하기 위해 `global: true`를 명시했습니다. 다른 token도 importing application의 `providers`나 imports에 의존하지 말고, 해당 token을 소유하고 export하는 module을 bootstrap 전에 global로 만드세요.
86
+
76
87
  ## 주요 패턴
77
88
 
78
89
  ### 서비스 트랜잭션 경계 (@Transaction)
@@ -161,20 +172,25 @@ await this.db.transaction(async () => {
161
172
 
162
173
  중첩 호출은 활성 transaction boundary를 재사용합니다. 이미 boundary가 활성화되어 있는데 중첩 호출이 transaction option을 전달하면, 기존 transaction을 조용히 바꾸지 않고 해당 중첩 option을 거부합니다.
163
174
 
164
- `database.transaction(...)`을 사용할 수 없고 `strictTransactions`가 `false`(기본값)이면 `transaction()`과 `requestTransaction()`은 의도적으로 fail-open(fail-open fallback)하여 callback을 root handle에서 직접 실행합니다. 이는 local fake, read-only adapter, 점진적 migration에는 유용하지만 원자적이지 않으므로 실제 데이터베이스 transaction으로 취급하면 안 됩니다. rollback 보장이 필요한 production 경로에서는 `strictTransactions: true`를 설정하세요. 그러면 startup 및 readiness 진단에서 누락된 `database.transaction(...)` 지원을 드러내고, transaction helper는 트랜잭션 없이 조용히 실행하는 대신 예외를 던집니다. 요청 범위 fallback 그래도 `AbortSignal`을 존중하므로, Drizzle transaction runner가 없어도 취소된 요청은 직접 실행 전이나 도중에 중단될 있습니다.
175
+ `database.transaction(...)`을 사용할 수 없고 `strictTransactions`가 `false`(기본값)이면 `transaction()`과 `requestTransaction()`은 의도적으로 fail-open(fail-open fallback)하여 callback을 root handle에서 직접 실행합니다. 이는 local fake, read-only adapter, 점진적 migration에는 유용하지만 원자적이지 않으므로 실제 데이터베이스 transaction으로 취급하면 안 됩니다. rollback 보장이 필요한 production 경로에서는 `strictTransactions: true`를 설정하세요. 그러면 startup 및 readiness 진단에서 누락된 `database.transaction(...)` 지원을 드러내고, transaction helper는 트랜잭션 없이 조용히 실행하는 대신 예외를 던집니다. Fail-open callback도 root-handle ALS context에서 실행되므로 중첩 helper는 fallback boundary를 재사용하고, 중첩 request 작업은 ambient request `AbortSignal`을 상속하며, shutdown은 dispose 전에 중첩 직접 실행을 drain합니다. context 보존은 rollback 원자성을 추가하지 않습니다.
176
+
177
+ Transaction 안에서 생성된 async 작업은 소유 transaction이 commit, rollback 또는 다른 방식으로 settle된 뒤 실행되더라도 ALS context를 상속할 수 있습니다. 이렇게 상속된 continuation에서 나중에 호출하는 `transaction(...)` 또는 `requestTransaction(...)`은 닫힌 transaction handle을 재사용하지 않고 lifecycle tracking이 적용된 새 root로 처리됩니다. Shutdown은 `dispose(database)` 전에 이 새 root를 drain하며, owner가 settle되기 전에 시작한 호출은 계속 활성 boundary를 공유합니다.
165
178
 
166
179
  ### 요청 전체 컨트롤러 경계
167
180
 
168
181
  비즈니스 작업에는 서비스 레벨 `@Transaction()`을 우선 사용하세요. 전체 요청을 하나의 transaction으로 감싸던 NestJS controller/interceptor 패턴을 마이그레이션해야 한다면 controller, route adapter, request orchestration 경계에서 `requestTransaction(...)`을 명시적으로 호출하고 가능한 경우 request `AbortSignal`을 전달하세요.
169
182
 
170
183
  ```ts
171
- import { Controller, Post } from '@fluojs/http';
184
+ import { Inject } from '@fluojs/core';
185
+ import { Controller, Post, type RequestContext } from '@fluojs/http';
172
186
  import { DrizzleDatabase } from '@fluojs/drizzle';
173
187
  import { drizzle } from 'drizzle-orm/node-postgres';
188
+ import { CheckoutService } from './checkout.service';
174
189
 
175
190
  type AppDatabase = ReturnType<typeof drizzle>;
176
191
 
177
192
  @Controller('/checkout')
193
+ @Inject(DrizzleDatabase, CheckoutService)
178
194
  export class CheckoutController {
179
195
  constructor(
180
196
  private readonly db: DrizzleDatabase<AppDatabase>,
@@ -182,20 +198,46 @@ export class CheckoutController {
182
198
  ) {}
183
199
 
184
200
  @Post()
185
- create(input: CheckoutInput, requestSignal?: AbortSignal) {
201
+ create(input: CheckoutInput, context: RequestContext) {
186
202
  return this.db.requestTransaction(
187
203
  () => this.checkout.createOrder(input),
188
- requestSignal,
204
+ context.request.signal,
189
205
  );
190
206
  }
191
207
  }
192
208
  ```
193
209
 
194
- import 있는 Drizzle `*TransactionInterceptor` export없습니다. 기존 NestJS interceptor 설계는 대부분의 transaction boundary를 서비스로 옮기고, 전체 request 작업이 서비스 메서드 하나가 아니라 같은 boundary를 공유해야 하는 드문 controller-level 호환성 사례에만 명시적 `requestTransaction(...)`을 남기세요. controller가 명시적 `DrizzleDatabase` 대상을 소유한다면 controller method에 `@Transaction()`을 붙이는 방식도 호환성 경로로 유지되지만, request `AbortSignal`을 직접 받을 수 있는 `requestTransaction(...)`이 더 명확한 request-wide API입니다.
210
+ `DrizzleTransactionInterceptor`는 기존 NestJS interceptor import 위한 deprecated 1.x 호환성 bridge입니다. 이 interceptor`requestTransaction(...)`에 위임하고 request `AbortSignal`을 전달합니다. 코드에서는 비즈니스 transaction boundary를 서비스로 옮기고, 전체 request 작업이 서비스 메서드 하나가 아니라 같은 boundary를 공유해야 하는 드문 controller-level 사례에만 명시적 `requestTransaction(...)`을 사용하세요. controller가 명시적 `DrizzleDatabase` 대상을 소유한다면 controller method에 `@Transaction()`을 붙이는 방식도 호환성 경로로 유지되지만, request `AbortSignal`을 직접 받을 수 있는 `requestTransaction(...)`이 더 명확한 request-wide API입니다.
211
+
212
+ ### 이름 있는 클라이언트
213
+
214
+ 추가 client는 비어 있지 않은 `name`으로 등록하고 `DrizzleDatabase` class token 대신 package-owned token을 주입합니다.
215
+
216
+ ```ts
217
+ const ANALYTICS_DRIZZLE = getDrizzleHandleProviderToken('analytics');
218
+
219
+ DrizzleModule.forRoot({ database: primaryDatabase });
220
+ DrizzleModule.forRoot({ database: analyticsDatabase, name: 'analytics' });
221
+
222
+ @Inject(ANALYTICS_DRIZZLE)
223
+ class AnalyticsService {
224
+ constructor(private readonly analytics: DrizzleDatabase<AnalyticsDatabase>) {}
225
+
226
+ @Transaction((self: AnalyticsService) => self.analytics)
227
+ async rebuild() {}
228
+ }
229
+ ```
230
+
231
+ `getDrizzleDatabaseToken`, `getDrizzleDisposeToken`, `getDrizzleOptionsToken`,
232
+ `getDrizzleHandleProviderToken`은 trim된 이름마다 서로 다른 안정적인 identity를 반환합니다. 이름 있는 client는
233
+ non-global이며 ALS transaction context, shutdown drain, disposal, status를 독립적으로 소유합니다. consumer는 일치하는
234
+ 이름 있는 token을 export하는 module을 import해야 하며, 이름이 runtime container를 분리하지는 않습니다. `name`을 생략하면
235
+ 기존 default token, `DrizzleDatabase` class token, interceptor 동작이 유지됩니다.
195
236
 
196
237
  ### 종료와 상태 계약
197
238
 
198
239
  애플리케이션 종료 중에는 `DrizzleDatabase`가 아직 활성 상태인 요청 트랜잭션을 abort하고, 열린 요청 및 수동 transaction callback이 settle되거나 rollback될 때까지 기다린 뒤 선택적 `dispose(database)` hook을 실행합니다. 여기에는 `database.transaction(...)`을 사용할 수 없고 `strictTransactions`가 `false`일 때의 fail-open 수동 `transaction(...)` callback도 포함되므로, 직접 실행 fallback도 pool이나 외부 관리 리소스를 닫기 전에 drain됩니다.
240
+ 상속한 owner가 settle된 뒤 새 boundary를 시작하는 transaction continuation은 닫힌 transaction handle을 더 이상 재사용하지 않습니다. 이 continuation은 독립적으로 tracking되는 root가 되며, shutdown은 disposal 전에 해당 continuation root를 기다립니다.
199
241
  기존 요청 boundary 안에서 열린 중첩 `requestTransaction(...)` 호출은 활성 Drizzle transaction을 재사용하면서도 ambient request abort signal을 관찰합니다. 기존 수동 transaction boundary 안에서 열린 중첩 `requestTransaction(...)` 호출도 두 번째 Drizzle transaction을 열지 않고 shutdown settlement tracking에 참여하며, 해당 settlement handle은 바깥 수동 transaction이 settle될 때까지 tracking에 남아 shutdown이 `dispose(database)`를 실행하기 전에 그 바깥 경계까지 drain하게 합니다. 단, platform status activity count는 더 짧게 유지됩니다. 중첩 request callback이 settle되는 즉시, 바깥 수동 transaction이 계속 실행 중이어도 `details.activeRequestTransactions`는 감소합니다.
200
242
  종료가 시작된 뒤 새 `transaction(...)` 및 `requestTransaction(...)` 호출은 거부되므로, 종료 boundary를 지난 뒤 시작되는 늦은 트랜잭션보다 dispose가 먼저 실행되는 상황을 방지합니다.
201
243
  요청 callback이 완료된 뒤 underlying Drizzle transaction runner가 commit 또는 rollback을 끝내기 전에 request signal이 abort되면, `requestTransaction(...)`은 먼저 해당 runner가 settle될 때까지 기다린 다음 abort reason으로 reject합니다. 이 동작은 Drizzle cleanup을 request cancellation과 직렬화하면서, 완료된 callback 결과를 반환하는 대신 늦은 request abort를 caller에게 드러냅니다.
@@ -232,8 +274,10 @@ defineModule(ManualDrizzleModule, {
232
274
  - `DrizzleModule.forRoot(options)` / `DrizzleModule.forRootAsync(options)`
233
275
  - `DrizzleDatabase`
234
276
  - `DrizzleDatabaseFacade<TDatabase>`
277
+ - `DrizzleTransactionInterceptor` (deprecated 1.x request-transaction compatibility bridge)
235
278
  - `Transaction`
236
279
  - `DRIZZLE_DATABASE`, `DRIZZLE_DISPOSE`, `DRIZZLE_HANDLE_PROVIDER`, `DRIZZLE_OPTIONS`
280
+ - `getDrizzleDatabaseToken(name?)`, `getDrizzleDisposeToken(name?)`, `getDrizzleHandleProviderToken(name?)`, `getDrizzleOptionsToken(name?)`
237
281
  - `DrizzleDatabase.createFacade(...)` (호환성 전용 provider wiring helper; 애플리케이션 등록은 `DrizzleModule.forRoot(...)` / `forRootAsync(...)`를 우선 사용)
238
282
  - `createDrizzlePlatformStatusSnapshot(...)`
239
283
  - `DrizzleDatabaseLike`
@@ -242,6 +286,8 @@ defineModule(ManualDrizzleModule, {
242
286
 
243
287
  `DRIZZLE_HANDLE_PROVIDER`는 lifecycle-aware `DrizzleDatabase` wrapper를 가리키는 alias token입니다. `@fluojs/terminus` 같은 health integration은 이 token을 통해 raw database ping으로 fallback하기 전에 `createPlatformStatusSnapshot()`을 읽습니다.
244
288
 
289
+ `DrizzleModule`은 importing module을 위해 `DRIZZLE_DATABASE`, `DRIZZLE_DISPOSE`, `DRIZZLE_OPTIONS`를 export합니다. `DRIZZLE_DATABASE`는 설정된 raw Drizzle handle을 주입하므로 lifecycle-aware facade와 ambient transaction handle 선택을 우회합니다. 애플리케이션 repository에는 `DrizzleDatabase` 또는 `DrizzleDatabaseFacade`를 우선 사용하고, 설정된 driver handle이 꼭 필요한 integration에만 raw token을 주입하세요. `DRIZZLE_DISPOSE`는 설정된 선택적 cleanup hook을, `DRIZZLE_OPTIONS`는 정규화된 runtime option을 노출합니다.
290
+
245
291
  provider가 `current()`, `transaction(...)`, `requestTransaction(...)`, `createPlatformStatusSnapshot()` 같은 wrapper 메서드만 필요로 하면 `DrizzleDatabase<TDatabase>`를 사용하세요. 리포지토리 주입에서 Drizzle query 메서드를 직접 호출해야 한다면 `DrizzleDatabaseFacade<TDatabase>`를 사용합니다. 이 facade는 활성 트랜잭션 handle이 있으면 그 handle로, 없으면 root handle로 호출을 전달합니다. `DrizzleDatabase.createFacade(...)`는 module provider wiring을 위한 low-level compatibility helper로 유지됩니다. 애플리케이션 코드는 `DrizzleModule.forRoot(...)` / `forRootAsync(...)`를 우선 사용하세요.
246
292
 
247
293
  `Transaction`은 서비스 계층 트랜잭션 경계를 위한 표준 TC39 method decorator입니다. 데코레이터가 붙은 host에서 `this.db`, 직접 property, 중첩 `.db` property 순서로 transaction-capable 대상을 resolve한 뒤, 후보가 없으면 데코레이터가 붙은 인스턴스 자체로 fallback합니다. 명시적 client 선택에는 accessor를 받을 수 있으며, 외부 경계에는 Drizzle transaction option을 전달할 수 있습니다.
@@ -252,6 +298,7 @@ provider가 `current()`, `transaction(...)`, `requestTransaction(...)`, `createP
252
298
  - `forRootAsync(...)`는 database/dispose/transaction 설정을 factory에서 반환하는 DI-aware Drizzle 옵션을 받습니다. provider를 전역으로 노출해야 할 때는 최상위 async 등록 옵션에 `global`을 전달하세요.
253
299
  - `forRootAsync(...)`는 애플리케이션 container마다 옵션을 한 번 resolve합니다. 테스트나 multi-app process에서 같은 module definition을 재사용해도 memoized factory result를 공유하지 않고 각 container가 독립적인 database/dispose 결과를 받습니다.
254
300
  - `strictTransactions: true`를 설정하면 transaction 지원이 없는 database handle에서 예외를 던집니다.
301
+ - 추가 이름 있는 등록은 non-global입니다. Consumer는 일치하는 `getDrizzle*Token(name)`을 export하는 module을 import하고 해당 token으로 주입하며, 이름이 runtime container를 분리하지는 않습니다. 각 등록은 독립 ALS transaction context, drain, disposal, status를 소유하고 `@Transaction((self) => self.analytics)`로 명시적으로 선택합니다.
255
302
  - sync 및 async 등록 모두에서 `database`는 실제 object/function handle이어야 하며, 누락된 handle은 모듈 등록 또는 async bootstrap 중 거부됩니다.
256
303
 
257
304
  ## 관련 패키지
package/README.md CHANGED
@@ -14,6 +14,7 @@ Node.js-only Drizzle ORM integration for fluo with a transaction-aware database
14
14
  - [Service Transaction Boundary (@Transaction)](#service-transaction-boundary-transaction)
15
15
  - [Manual Transactions and current()](#manual-transactions-and-current)
16
16
  - [Request-Wide Controller Boundaries](#request-wide-controller-boundaries)
17
+ - [Named clients](#named-clients)
17
18
  - [Shutdown and Status Contracts](#shutdown-and-status-contracts)
18
19
  - [Manual Module Composition](#manual-module-composition)
19
20
  - [Public API Overview](#public-api-overview)
@@ -23,14 +24,16 @@ Node.js-only Drizzle ORM integration for fluo with a transaction-aware database
23
24
  ## Installation
24
25
 
25
26
  ```bash
26
- npm install @fluojs/drizzle drizzle-orm
27
+ npm install @fluojs/drizzle drizzle-orm@^0.45.2
27
28
  # Install the driver for your Drizzle adapter as well, for example:
28
29
  npm install pg
29
30
  ```
30
31
 
32
+ `@fluojs/drizzle` requires Drizzle ORM `>=0.45.2`. Consumers using an older Drizzle ORM release must upgrade the peer and refresh their lockfile before adopting this major `@fluojs/drizzle` release. The fluo integration API is unchanged, but applications should run their driver-specific query and migration tests against the upgraded ORM.
33
+
31
34
  ## Runtime Support
32
35
 
33
- The root `@fluojs/drizzle` package is currently a Node.js 20+ integration. It imports Node's `node:async_hooks` module to maintain the ambient transaction context and the package manifest declares `engines.node >=20.0.0`.
36
+ The root `@fluojs/drizzle` package requires Node.js `>=24.0.0 <27`. It imports Node's `node:async_hooks` module to maintain the ambient transaction context and its package manifest declares that package-owned support contract. Upgrade Node 20 and Node 22 hosts to Node.js `>=24.0.0 <27`; Node versions below 24 and Node 27+ are unsupported.
34
37
 
35
38
  Drizzle ORM itself can target drivers such as Bun SQL or Cloudflare D1, but those driver runtimes are outside this fluo wrapper until a non-Node transaction-context adapter is documented.
36
39
 
@@ -38,14 +41,14 @@ Non-Node runtimes should not import the root package. For Bun, Deno, Cloudflare
38
41
 
39
42
  ## When to Use
40
43
 
41
- - when a Node.js 20+ application needs Drizzle to participate in the same module, DI, and lifecycle model as the rest of the app
44
+ - when an application running Node.js `>=24.0.0 <27` needs Drizzle to participate in the same module, DI, and lifecycle model as the rest of the app
42
45
  - when repositories need a single `current()` seam that switches between the root handle and the active transaction handle
43
46
  - when application shutdown should also run an explicit cleanup hook for the underlying driver resources
44
47
 
45
48
  ## Quick Start
46
49
 
47
50
  ```ts
48
- import { ConfigService } from '@fluojs/config';
51
+ import { ConfigModule, ConfigService } from '@fluojs/config';
49
52
  import { Module } from '@fluojs/core';
50
53
  import { DrizzleModule } from '@fluojs/drizzle';
51
54
  import { drizzle } from 'drizzle-orm/node-postgres';
@@ -53,6 +56,12 @@ import { Pool } from 'pg';
53
56
 
54
57
  @Module({
55
58
  imports: [
59
+ ConfigModule.forRoot({
60
+ global: true,
61
+ processEnv: {
62
+ DATABASE_URL: process.env.DATABASE_URL,
63
+ },
64
+ }),
56
65
  DrizzleModule.forRootAsync({
57
66
  inject: [ConfigService],
58
67
  useFactory: async (config: ConfigService) => {
@@ -73,6 +82,8 @@ import { Pool } from 'pg';
73
82
  export class AppModule {}
74
83
  ```
75
84
 
85
+ `forRootAsync(...)` accepts only `inject` and `useFactory` for its factory dependencies; it does not discover NestJS `imports`, `useClass`, `useExisting`, or decorator metadata. Its generated async module has no `imports`, so a token exported only by a sibling module or by a parent module's import is not visible to the options provider. Register factory dependencies through a global module instead. The `ConfigModule.forRoot(...)` registration above exports `ConfigService` globally by default; `global: true` is shown explicitly because that global export makes `ConfigService` visible to the generated async Drizzle module. For another token, make the module that owns and exports it global before bootstrap rather than relying on the importing application's `providers` or imports.
86
+
76
87
  ## Common Patterns
77
88
 
78
89
  ### Service Transaction Boundary (@Transaction)
@@ -161,20 +172,25 @@ await this.db.transaction(async () => {
161
172
 
162
173
  Nested calls reuse the active transaction boundary. If a nested call passes transaction options while a boundary is already active, the package rejects those nested options instead of silently changing the existing transaction.
163
174
 
164
- When `database.transaction(...)` is unavailable and `strictTransactions` is `false` (the default), `transaction()` and `requestTransaction()` intentionally fail open (fail-open fallback) by running the callback directly against the root handle. This is useful for local fakes, read-only adapters, or gradual migrations, but it is not atomic and should not be treated as a real database transaction. Set `strictTransactions: true` in production paths that require rollback guarantees; startup and readiness diagnostics then surface missing `database.transaction(...)` support and transaction helpers throw instead of silently running without a transaction. Request-scoped fallback still honors `AbortSignal`, so a cancelled request can stop before or during direct execution even though no Drizzle transaction runner exists.
175
+ When `database.transaction(...)` is unavailable and `strictTransactions` is `false` (the default), `transaction()` and `requestTransaction()` intentionally fail open (fail-open fallback) by running the callback directly against the root handle. This is useful for local fakes, read-only adapters, or gradual migrations, but it is not atomic and should not be treated as a real database transaction. Set `strictTransactions: true` in production paths that require rollback guarantees; startup and readiness diagnostics then surface missing `database.transaction(...)` support and transaction helpers throw instead of silently running without a transaction. Fail-open callbacks still run in a root-handle ALS context, so nested helpers reuse the fallback boundary, nested request work inherits the ambient request `AbortSignal`, and shutdown drains nested direct execution before disposal. This context preservation does not add rollback atomicity.
176
+
177
+ Async work created inside a transaction can inherit its ALS context even when it runs after the owning transaction has committed, rolled back, or otherwise settled. A later `transaction(...)` or `requestTransaction(...)` call from that inherited continuation is treated as a fresh lifecycle-tracked root instead of reusing the closed transaction handle. Shutdown drains that fresh root before `dispose(database)`, while calls that begin before the owner settles continue to share the active boundary.
165
178
 
166
179
  ### Request-Wide Controller Boundaries
167
180
 
168
181
  Prefer service-level `@Transaction()` for business operations. If you are migrating a NestJS controller/interceptor pattern where an entire request must be transactional, call `requestTransaction(...)` explicitly at the controller, route adapter, or request orchestration boundary and pass the request `AbortSignal` when one is available:
169
182
 
170
183
  ```ts
171
- import { Controller, Post } from '@fluojs/http';
184
+ import { Inject } from '@fluojs/core';
185
+ import { Controller, Post, type RequestContext } from '@fluojs/http';
172
186
  import { DrizzleDatabase } from '@fluojs/drizzle';
173
187
  import { drizzle } from 'drizzle-orm/node-postgres';
188
+ import { CheckoutService } from './checkout.service';
174
189
 
175
190
  type AppDatabase = ReturnType<typeof drizzle>;
176
191
 
177
192
  @Controller('/checkout')
193
+ @Inject(DrizzleDatabase, CheckoutService)
178
194
  export class CheckoutController {
179
195
  constructor(
180
196
  private readonly db: DrizzleDatabase<AppDatabase>,
@@ -182,20 +198,46 @@ export class CheckoutController {
182
198
  ) {}
183
199
 
184
200
  @Post()
185
- create(input: CheckoutInput, requestSignal?: AbortSignal) {
201
+ create(input: CheckoutInput, context: RequestContext) {
186
202
  return this.db.requestTransaction(
187
203
  () => this.checkout.createOrder(input),
188
- requestSignal,
204
+ context.request.signal,
189
205
  );
190
206
  }
191
207
  }
192
208
  ```
193
209
 
194
- There is no Drizzle `*TransactionInterceptor` export to import. Existing NestJS interceptor designs should move most transaction boundaries to services and reserve explicit `requestTransaction(...)` for rare controller-level compatibility cases where all request work, not just a service method, must share the same boundary. Decorating a controller method with `@Transaction()` remains a compatibility path when the controller owns an explicit `DrizzleDatabase` target, but `requestTransaction(...)` is the clearer request-wide API because it can receive the request `AbortSignal` directly.
210
+ `DrizzleTransactionInterceptor` is a deprecated 1.x compatibility bridge for existing NestJS interceptor imports. It delegates to `requestTransaction(...)` and forwards the request `AbortSignal`. New code should move business transaction boundaries to services and reserve explicit `requestTransaction(...)` for rare controller-level cases where all request work, not just a service method, must share the same boundary. Decorating a controller method with `@Transaction()` remains a compatibility path when the controller owns an explicit `DrizzleDatabase` target, but `requestTransaction(...)` is the clearer request-wide API because it can receive the request `AbortSignal` directly.
211
+
212
+ ### Named clients
213
+
214
+ Register each additional client with a non-empty `name` and inject its package-owned token instead of the `DrizzleDatabase` class token:
215
+
216
+ ```ts
217
+ const ANALYTICS_DRIZZLE = getDrizzleHandleProviderToken('analytics');
218
+
219
+ DrizzleModule.forRoot({ database: primaryDatabase });
220
+ DrizzleModule.forRoot({ database: analyticsDatabase, name: 'analytics' });
221
+
222
+ @Inject(ANALYTICS_DRIZZLE)
223
+ class AnalyticsService {
224
+ constructor(private readonly analytics: DrizzleDatabase<AnalyticsDatabase>) {}
225
+
226
+ @Transaction((self: AnalyticsService) => self.analytics)
227
+ async rebuild() {}
228
+ }
229
+ ```
230
+
231
+ `getDrizzleDatabaseToken`, `getDrizzleDisposeToken`, `getDrizzleOptionsToken`, and
232
+ `getDrizzleHandleProviderToken` return distinct stable identities for each trimmed name. Named clients are
233
+ non-global and independently own ALS transaction context, shutdown drain, disposal, and status. A consumer must import
234
+ a module that exports the matching named token; names do not create isolated runtime containers. Omitting `name`
235
+ preserves the existing default tokens, `DrizzleDatabase` class token, and interceptor behavior.
195
236
 
196
237
  ### Shutdown and status contracts
197
238
 
198
239
  During application shutdown, `DrizzleDatabase` aborts any still-active request transaction, waits for open request and manual transaction callbacks to settle or roll back, and only then runs the optional `dispose(database)` hook. This includes fail-open manual `transaction(...)` callbacks when `database.transaction(...)` is unavailable and `strictTransactions` is `false`, so direct-execution fallbacks still drain before pools or externally managed resources are closed.
240
+ Transaction continuations that start a new boundary after their inherited owner settles no longer reuse the closed transaction handle. They become independently tracked roots, and shutdown waits for those continuation roots before disposal.
199
241
  Nested `requestTransaction(...)` calls opened inside an existing request boundary observe the ambient request abort signal while still reusing the active Drizzle transaction. Nested `requestTransaction(...)` calls opened inside an existing manual transaction boundary also join shutdown settlement tracking without opening a second Drizzle transaction, and their settlement handle remains tracked until the outer manual transaction settles so shutdown drains that outer boundary before `dispose(database)` runs. The platform status activity count is intentionally shorter lived: once the nested request callback settles, `details.activeRequestTransactions` is decremented even if the outer manual transaction continues running.
200
242
  New `transaction(...)` and `requestTransaction(...)` calls are rejected once shutdown begins, so disposal cannot overtake a late transaction that starts after the shutdown boundary is crossed.
201
243
  If the request signal aborts after the request callback has completed but before the underlying Drizzle transaction runner finishes committing or rolling back, `requestTransaction(...)` waits for that runner to settle first and then rejects with the abort reason. This keeps Drizzle cleanup serialized with request cancellation while making the late request abort visible to the caller instead of returning the completed callback result.
@@ -232,8 +274,10 @@ defineModule(ManualDrizzleModule, {
232
274
  - `DrizzleModule.forRoot(options)` / `DrizzleModule.forRootAsync(options)`
233
275
  - `DrizzleDatabase`
234
276
  - `DrizzleDatabaseFacade<TDatabase>`
277
+ - `DrizzleTransactionInterceptor` (deprecated 1.x request-transaction compatibility bridge)
235
278
  - `Transaction`
236
279
  - `DRIZZLE_DATABASE`, `DRIZZLE_DISPOSE`, `DRIZZLE_HANDLE_PROVIDER`, `DRIZZLE_OPTIONS`
280
+ - `getDrizzleDatabaseToken(name?)`, `getDrizzleDisposeToken(name?)`, `getDrizzleHandleProviderToken(name?)`, `getDrizzleOptionsToken(name?)`
237
281
  - `DrizzleDatabase.createFacade(...)` (compatibility-only provider wiring helper; prefer `DrizzleModule.forRoot(...)` / `forRootAsync(...)` for application registration)
238
282
  - `createDrizzlePlatformStatusSnapshot(...)`
239
283
  - `DrizzleDatabaseLike`
@@ -242,6 +286,8 @@ defineModule(ManualDrizzleModule, {
242
286
 
243
287
  `DRIZZLE_HANDLE_PROVIDER` is an alias token for the lifecycle-aware `DrizzleDatabase` wrapper. Health integrations such as `@fluojs/terminus` use this token to read `createPlatformStatusSnapshot()` before falling back to raw database pings.
244
288
 
289
+ `DrizzleModule` exports `DRIZZLE_DATABASE`, `DRIZZLE_DISPOSE`, and `DRIZZLE_OPTIONS` for importing modules. `DRIZZLE_DATABASE` injects the configured raw Drizzle handle, so it bypasses the lifecycle-aware facade and ambient transaction-handle selection. Prefer `DrizzleDatabase` or `DrizzleDatabaseFacade` for application repositories; inject the raw token only for integrations that require the configured driver handle. `DRIZZLE_DISPOSE` exposes the configured optional cleanup hook, and `DRIZZLE_OPTIONS` exposes normalized runtime options.
290
+
245
291
  Use `DrizzleDatabase<TDatabase>` when a provider only needs wrapper methods such as `current()`, `transaction(...)`, `requestTransaction(...)`, or `createPlatformStatusSnapshot()`. Use `DrizzleDatabaseFacade<TDatabase>` for repository injections that call Drizzle query methods directly; the facade forwards those calls to the active transaction handle when one exists and to the root handle otherwise. `DrizzleDatabase.createFacade(...)` is retained as a low-level compatibility helper for module-provider wiring; application code should prefer `DrizzleModule.forRoot(...)` / `forRootAsync(...)`.
246
292
 
247
293
  `Transaction` is a standard TC39 method decorator for service-layer transaction boundaries. It resolves a transaction-capable target from the decorated host by checking `this.db`, then direct properties, then nested `.db` properties, then falling back to the decorated instance itself; it also accepts an accessor for explicit client selection and can forward Drizzle transaction options to the outer boundary.
@@ -252,6 +298,7 @@ Use `DrizzleDatabase<TDatabase>` when a provider only needs wrapper methods such
252
298
  - `forRootAsync(...)` accepts DI-aware Drizzle options whose factory returns the database/dispose/transaction settings; pass `global` on the top-level async registration when the providers should be visible globally.
253
299
  - `forRootAsync(...)` resolves options once per application container. Reusing the same module definition across tests or multi-app processes creates isolated database/dispose results for each container instead of sharing a memoized factory result.
254
300
  - Supports `strictTransactions: true` to throw if transaction support is missing.
301
+ - Additional named registrations are non-global. Consumers import a module that exports the matching `getDrizzle*Token(name)` and inject through that token; names do not create isolated runtime containers. Each registration owns independent ALS transaction context, drain, disposal, and status; select it explicitly with `@Transaction((self) => self.analytics)`.
255
302
  - `database` must be a concrete object/function handle for both sync and async registration; missing handles are rejected during module registration or async bootstrap.
256
303
 
257
304
  ## Related Packages
@@ -82,8 +82,12 @@ export declare class DrizzleDatabase<TDatabase extends DrizzleDatabaseLike<TTran
82
82
  private executeTransaction;
83
83
  private executeManualRootTransaction;
84
84
  private executeRequestTransaction;
85
+ private executeRequestRootTransaction;
86
+ private executeInheritedRequestTransaction;
87
+ private executeNestedManualTransaction;
85
88
  private executeNestedRequestTransaction;
86
89
  private executeRequestFallback;
90
+ private closeTransactionBoundaryOwner;
87
91
  private assertRequestTransactionsAvailable;
88
92
  private assertTransactionsAvailable;
89
93
  private throwIfRequestAborted;
@@ -1 +1 @@
1
- {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"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"}